gridtext/0000755000176200001440000000000013764504772012123 5ustar liggesusersgridtext/NAMESPACE0000644000176200001440000000110613764206777013344 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(ascentDetails,richtext_grob) S3method(ascentDetails,textbox_grob) S3method(descentDetails,richtext_grob) S3method(descentDetails,textbox_grob) S3method(heightDetails,richtext_grob) S3method(heightDetails,textbox_grob) S3method(makeContent,textbox_grob) S3method(makeContext,textbox_grob) S3method(widthDetails,richtext_grob) S3method(widthDetails,textbox_grob) export(richtext_grob) export(textbox_grob) import(grid) import(rlang) importFrom(Rcpp,sourceCpp) importFrom(xml2,read_html) useDynLib(gridtext, .registration = TRUE) gridtext/LICENSE0000644000176200001440000000005413610076272013114 0ustar liggesusersYEAR: 2020 COPYRIGHT HOLDER: Claus O. Wilke gridtext/README.md0000644000176200001440000001576613760022324013401 0ustar liggesusers gridtext ======== [![R build status](https://github.com/wilkelab/gridtext/workflows/R-CMD-check/badge.svg)](https://github.com/wilkelab/gridtext/actions) [![Coverage Status](https://img.shields.io/codecov/c/github/wilkelab/gridtext/master.svg)](https://codecov.io/github/wilkelab/gridtext?branch=master) [![CRAN status](https://www.r-pkg.org/badges/version/gridtext)](https://cran.r-project.org/package=gridtext) [![Lifecycle: maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing) Improved text rendering support for grid graphics in R. Installation ------------ You can install the current release from CRAN with `install.packages()`: install.packages("gridtext") To install the latest development version of this package, please run the following line in your R console: remotes::install_github("wilkelab/gridtext") Examples -------- The gridtext package provides two new grobs, `richtext_grob()` and `textbox_grob()`, which support drawing of formatted text labels and formatted text boxes, respectively. Both grobs understand an extremely limited subset of Markdown, HTML, and CSS directives. The idea is to provide a minimally useful subset of features. These currently include italics, bold, super- and subscript, as well as changing text color, font, and font size via inline CSS. Extremely limited support for images is also provided. Note that all text rendering is performed through a custom-built rendering pipeline that is part of the gridtext package. This approach has several advantages, including minimal dependencies, good performance, and compatibility with all R graphics devices (to the extent that the graphics devices support the fonts you want to use). The downside of this approach is the severely limited feature set. Don’t expect this package to support the fancy CSS and javascript tricks you’re used to when designing web pages. ### Richtext grob The function `richtext_grob()` serves as a replacement for `textGrob()`. It is vectorized and can draw multiple text labels with one call. Labels can be drawn with padding, margins, and at arbitrary angles. Markdown and HTML parsing is turned on by default. library(grid) library(gridtext) text <- c( "Some text **in bold.**", "Linebreaks
Linebreaks
Linebreaks", "*x*2 + 5*x* + *C**i*", "Some blue text **in bold.**
And *italics text.*
And some large text." ) x <- c(.2, .1, .7, .9) y <- c(.8, .4, .1, .5) rot <- c(0, 0, 45, -45) gp = gpar( col = c("black", "red"), fontfamily = c("Palatino", "Courier", "Times", "Helvetica") ) box_gp = gpar( col = "black", fill = c(NA, "cornsilk", "lightblue1", NA), lty = c(0, 1, 1, 1) ) hjust <- c(0.5, 0, 0, 1) vjust <- c(0.5, 1, 0, 0.5) grid.newpage() g <- richtext_grob( text, x, y, hjust = hjust, vjust = vjust, rot = rot, padding = unit(c(6, 6, 4, 6), "pt"), r = unit(c(0, 2, 4, 8), "pt"), gp = gp, box_gp = box_gp ) grid.draw(g) grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) ![](man/figures/README-unnamed-chunk-4-1.png) The boxes around text labels can be set to have matching widths and/or heights, and alignment of text inside the box (specified via `hjust` and `vjust`) is separate from alignment of the box relative to a reference point (specified via `box_hjust` and `box_vjust`). text <- c("January", "February", "March", "April", "May") x <- (1:5)/6 + 1/24 y <- rep(0.8, 5) g <- richtext_grob( text, x, y, halign = 0, hjust = 1, rot = 45, padding = unit(c(3, 6, 1, 3), "pt"), r = unit(4, "pt"), align_widths = TRUE, box_gp = gpar(col = "black", fill = "cornsilk") ) grid.newpage() grid.draw(g) grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) ![](man/figures/README-unnamed-chunk-5-1.png) Basic support for images is available as well. As of now, images will always be vertically aligned with the baseline of the text. grid.newpage() img_src <- system.file("extdata", "Rlogo.png", package = "gridtext") text <- glue::glue("Image with native aspect ratio: And some more text.") grid.draw(richtext_grob(text, x = 0.9, y = 0.7, hjust = 1)) text <- glue::glue("Image with forced size: And some more text.") grid.draw(richtext_grob(text, x = 0.9, y = 0.3, hjust = 1)) ![](man/figures/README-unnamed-chunk-6-1.png) ### Textbox grob The function `textbox_grob()` is intended to render multi-line text labels that require automatic word wrapping. It is similar to `richtext_grob()`, but there are a few important differences. First, while `richtext_grob()` is vectorized, `textbox_grob()` is not. It can draw only a single text box at a time. Second, `textbox_grob()` doesn’t support rendering the text box at arbitrary angles. Only four different orientations are supported, corresponding to a rotation by 0, 90, 180, and 270 degrees. g <- textbox_grob( "**The quick brown fox jumps over the lazy dog.**

The quick brown fox jumps over the lazy dog. The **quick brown fox** jumps over the lazy dog. The quick brown fox jumps over the lazy dog.", x = unit(0.5, "npc"), y = unit(0.7, "npc"), gp = gpar(fontsize = 15), box_gp = gpar(col = "black", fill = "lightcyan1"), r = unit(5, "pt"), padding = unit(c(10, 10, 10, 10), "pt"), margin = unit(c(0, 10, 0, 10), "pt") ) grid.newpage() grid.draw(g) ![](man/figures/README-unnamed-chunk-7-1.png) The alignment parameters `hjust`, `vjust`, `halign`, and `valign` function just like they do in `richtext_grob()`. g <- textbox_grob( "**The quick brown fox jumps over the lazy dog.**

The quick brown fox jumps over the lazy dog. The **quick brown fox** jumps over the lazy dog. The quick brown fox jumps over the lazy dog.", x = unit(0.2, "npc"), y = unit(0.5, "npc"), hjust = 0.5, vjust = 1, halign = 1, gp = gpar(fontsize = 15), box_gp = gpar(col = "black", fill = "lightcyan1"), r = unit(5, "pt"), padding = unit(c(10, 10, 10, 10), "pt"), margin = unit(c(0, 10, 0, 10), "pt"), orientation = "left-rotated" ) grid.newpage() grid.draw(g) ![](man/figures/README-unnamed-chunk-8-1.png) Acknowledgments --------------- This project is receiving [financial support](https://www.r-consortium.org/projects/awarded-projects) from the [R consortium.](https://www.r-consortium.org) gridtext/man/0000755000176200001440000000000013604245316012663 5ustar liggesusersgridtext/man/gridtext.Rd0000644000176200001440000000067113604245154015010 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/gridtext.R \docType{package} \name{gridtext} \alias{gridtext} \title{Improved text rendering support for grid graphics} \description{ The gridtext package provides two new grobs, \code{\link[=richtext_grob]{richtext_grob()}} and \code{\link[=textbox_grob]{textbox_grob()}}, which support drawing of formatted text labels and formatted text boxes, respectively. } gridtext/man/textbox_grob.Rd0000644000176200001440000001633013610075065015662 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/textbox-grob.R \name{textbox_grob} \alias{textbox_grob} \title{Draw formatted multi-line text with word wrap} \usage{ textbox_grob( text, x = NULL, y = NULL, width = unit(1, "npc"), height = NULL, minwidth = NULL, maxwidth = NULL, minheight = NULL, maxheight = NULL, hjust = 0.5, vjust = 0.5, halign = 0, valign = 1, default.units = "npc", margin = unit(c(0, 0, 0, 0), "pt"), padding = unit(c(0, 0, 0, 0), "pt"), r = unit(0, "pt"), orientation = c("upright", "left-rotated", "right-rotated", "inverted"), name = NULL, gp = gpar(), box_gp = gpar(col = NA), vp = NULL, use_markdown = TRUE ) } \arguments{ \item{text}{Character vector containing Markdown/HTML string to draw.} \item{x, y}{Unit objects specifying the location of the reference point. If set to \code{NULL} (the default), these values are chosen based on the values of \code{hjust} and \code{vjust} such that the box is appropriately justified in the enclosing viewport.} \item{width, height}{Unit objects specifying width and height of the grob. A value of \code{NULL} means take up exactly the space necessary to render all content. Use a value of \code{unit(1, "npc")} to have the box take up all available space.} \item{minwidth, minheight, maxwidth, maxheight}{Min and max values for width and height. Set to \code{NULL} to impose neither a minimum nor a maximum. Note: \code{minheight} and \code{maxheight} do not work if \code{width = NULL}.} \item{hjust, vjust}{Numerical values specifying the justification of the text box relative to the reference point defined by \code{x} and \code{y}. These justification parameters are specified in the internal reference frame of the text box, so that, for example, \code{hjust} adjusts the vertical justification when the text box is left- or right-rotated.} \item{halign, valign}{Numerical values specifying the justification of the text inside the text box.} \item{default.units}{Units of \code{x}, \code{y}, \code{width}, \code{height}, \code{minwidth}, \code{minheight}, \code{maxwidth}, \code{maxheight} if these are provided only as numerical values.} \item{margin, padding}{Unit vectors of four elements each indicating the margin and padding around each text label in the order top, right, bottom, left. Margins are drawn outside the enclosing box (if any), and padding is drawn inside. To avoid rendering artifacts, it is best to specify these values in absolute units (such as points, mm, or inch) rather than in relative units (such as npc).} \item{r}{The radius of the rounded corners. To avoid rendering artifacts, it is best to specify this in absolute units (such as points, mm, or inch) rather than in relative units (such as npc).} \item{orientation}{Orientation of the box. Allowed values are \code{"upright"}, \code{"left-rotated"}, \code{"right-rotated"}, and \code{"inverted"}, corresponding to a rotation by 0, 90, 270, and 180 degrees counter-clockwise, respectively.} \item{name}{Name of the grob.} \item{gp}{Other graphical parameters for drawing.} \item{box_gp}{Graphical parameters for the enclosing box around each text label.} \item{vp}{Viewport.} \item{use_markdown}{Should the \code{text} input be treated as markdown?} } \value{ A grid \code{\link{grob}} that represents the formatted text. } \description{ The function \code{textbox_grob()} is intended to render multi-line text labels that require automatic word wrapping. It is similar to \code{\link[=richtext_grob]{richtext_grob()}}, but there are a few important differences. First, while \code{\link[=richtext_grob]{richtext_grob()}} is vectorized, \code{textbox_grob()} is not. It can draw only a single text box at a time. Second, \code{textbox_grob()} doesn't support rendering the text box at arbitrary angles. Only four different orientations are supported, corresponding to a rotation by 0, 90, 180, and 270 degrees. } \examples{ library(grid) g <- textbox_grob( "**The quick brown fox jumps over the lazy dog.**

The quick brown fox jumps over the lazy dog. The **quick brown fox** jumps over the lazy dog. The quick brown fox jumps over the lazy dog.", x = unit(0.5, "npc"), y = unit(0.7, "npc"), halign = 0, valign = 1, gp = gpar(fontsize = 15), box_gp = gpar(col = "black", fill = "lightcyan1"), r = unit(5, "pt"), padding = unit(c(10, 10, 10, 10), "pt"), margin = unit(c(0, 10, 0, 10), "pt") ) grid.newpage() grid.draw(g) # internal vs. external alignment g1 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, halign = 0, valign = 1, width = unit(1.5, "inch"), height = unit(1.5, "inch"), box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g2 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 1, halign = 0.5, valign = 0.5, width = unit(1.5, "inch"), height = unit(1.5, "inch"), box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g3 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 0, halign = 1, valign = 1, width = unit(1.5, "inch"), height = unit(1.5, "inch"), box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g4 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 0, halign = 0, valign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) grid.newpage() grid.draw(g1) grid.draw(g2) grid.draw(g3) grid.draw(g4) # internal vs. external alignment, with rotated boxes g1 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 1, halign = 0, valign = 1, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "left-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g2 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, halign = 0.5, valign = 0.5, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "right-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g3 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 1, halign = 1, valign = 1, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "inverted", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g4 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 0, halign = 0, valign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "upright", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) grid.newpage() grid.draw(g1) grid.draw(g2) grid.draw(g3) grid.draw(g4) } \seealso{ \code{\link[=richtext_grob]{richtext_grob()}} } gridtext/man/figures/0000755000176200001440000000000013760022324014322 5ustar liggesusersgridtext/man/figures/README-unnamed-chunk-7-1.png0000644000176200001440000010332313760022323021023 0ustar liggesusersPNG  IHDRz4iCCPkCGColorSpaceGenericRGB8U]hU>sg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U\ 8eXIfMM*i_@IDATx L1bsNb@(戊3bbΊYň @s@APwbkifvgfwgw=ݕ[7U ! B@!0GQ5B@! B# TA! B-)ܪL! B@ B@! @IZRUB@! P! B@" p2! B@1B@! %E@ hIVeB@! b@B@! JВ­ʄB@! Ā! B@1%[ ! B@UB@! ()b@K *B@! > B@!PRĀnU&B@! T}@! B-)ܪL! B@ B@! @IZRUB@! P! B@" p2! B@1B@! %E@ hIVeB@! b@B@! JВ­ʄB@! Ā! B@1%[ ! B@UB@! ()b@K *B@! > B@!PRĀnU&B@! T}@! B-)ܪL! B@ B@! @IZRUB@! P! B@" p2! B@1B@! %E@ hIVeB@! b@B@! JВ­ʄB@! Ā! B@1%[ ! B@UB@! ()b@K *B@! > B@!PRĀnU&B@! T}@! B-)ܪL! B@ B@! @IZRUB@! P! B@" p2! B@1B@! %E@ hIVeB@! b@B@! JВ­ʄB@! Ā! B@1%[ ! B@UB@! ()b@K *B@! > B@!PRĀnU&B@! T}@! B-)ܪL! B@ B@! @IZRUB@! P! B@" p2! B@1B@! %E`6i$wO^ir+Pb_! B иqcz܎o-"jYQ%<裮 'wmnWt#umyE*B@! @=C3ܸQK}6שS[Y4k>ܭE7@B@! ol3+x^ŀ2M۶_to_L#B@!P;m>rkFOQy n;+T! B@Gkq_y衂`tnvM\ A! B}dסeK I&=PaƍXf1B@! hX,BK>+2e[@ςV! B@44pLMZ(̀-B@! h`*-B@! 1 B@! JВJB@! b@: ! B@1%Y! B@ '}=w5m[hE݀~|LӦMs1~辴x&[mUpL)޶WeW\ѭ:~;e˝wv7.${JKdaoF&3̛oUVq5]`>Կro-X֯jO>}U'o.Iߪ8S{hV~p 3Zn֭ +@vcAnγ^{;}N_ML |ƥqǹo>W\^~ILvvO<=pa\j~J==]~=mҏ@-[>aڇN7]wX?7y"MNIuǙ1sv۹eVX=#6Ыk&mо}qGyں&nj;|Ȣ/d_v+_1~ڳϺ_'Nj3c3=wm[Y3G ǎ/t3D^{ln#*Im}7}i-'_ i6<1#ug1GF9+ueoFl_V_;u~UǏO_3Ͼ[z*}OL_7>eK^^nFӘ Oޥ[nuO}ttY}|ǎMXf6MtYN80}}VJ_5k߳ _c7uS߾y/zz={f6yB{=t#8#k݇>tw-}}]vI_? C/q!2|eF .gLq:,nqOgoOճ,ݺˠ]t.ҼϓK-3vPQv|0^/;Z!cC>lљe^cӄ6qܪS'>~8+We36ZmtYmR/6!s3f_?˻m9 ҅#io䚇N 7>x\q/{/eNa >Gf6eu6qL~#N?}w{:L Q~ȧc{ap1\m/qW[8^ƈdhL]y^"z%.eMa3ȯt&uaQ&q5i&s8Q /h$<'7qn!kב;3 tvޞ=2/v?`g O3̖uT45%.!Q9Ф?D2قcp@q{7*܈-_@8!bR7fLӦNuǛTIA'slrw"isZ1aΘ jo9̤}uΘIp^z T ¡YgR{ߤ:ͤwo漣vv%Mă6Ii~g{\# ȩ&l?"3"ښT.LqbD*w p k Ęf - c(E[ [=a.Z xh.roazy?&}M]W^}q Ipb%o! ja@$6R&TkmPy1u; Z IMZ.1G Ѝ=zUB7k2saNNk8يܽW9jɌ 0"+[h F`O"w`nq*aL䮰IdP;~of=;]S?iCEd3o;-IZ뗝|CTh`S@Ij]wu,p/~8o40}դ+~BZ̓FB\x=fM1_mӌQ>lfWgq-YHbIuBqda|-0ecFRH?55joDz,xc*f2vu$3n֊fZs[H}3o8L3| 43fCw} :4lj;=-Xo3"h4"U a޲PmoBp}|c5Ya ZPC,B6[l^eceַs&uNvpr`Ⱶ/3N]bb^4:cÑY/i /np< g|&ve` XB||\zvh0&4hTyJ %c+ R6[qQQ?Yh=L{UHB}~ġdlc[=!7x !PʆEER@A 6PS #6& Џ op7B?g=XEK!0=Qy$q?x`׫:>dX ĄtIV JH;5 D| fp/TL,48 @H?ՙS Xa?8ࠒf!Y& 1)!9)b0$؁lS̴3{IPSC[lW-E:{>,qāp^L?B' wQXTglX>C{=&0Q.@{ق/cX[Bl y.᳇iyxG#.PX8a9AC̢*&i ٠zoq 3\L$&VIRnKThx]\G`lQxap 'I'XBFav/HqO%N옒{i *d\}<6}WD3W:f1M3@$>q[I d dN3`a'6%olԃG*\ hs1~>xK>0XP<΃H.!1 *c7cNdq1U'%؆Bxr#f<ҵ0c/D_$JVZL? ]%x $[[:c9 %Zɨ{ =GO\ȓq1 1 :w<3;A8n2[dB5I.~=p$*"Mb~g[} v1m%\Al|?X2\300peCAm%?٤?xU_v*zCsZ g,)y ۰;͸ԡ08H(XۢŘ$k!< f#De$=~"|i]N䂃mii𮫮 ]6}fKϵ`FWLb$p8Tx 1g1^A{~4Ў|*6;f÷i(Sa:Go}I@70F#_~lL*,AN]q?.tlHlmB_,A(z{Œ[W88⼕c3`7g-f$i]fG|lqTR&wy^jI*$LؘN {<$%o! jQc'!gxW~k5Z-$8$"쭂X~Hj~[fɆ uhMҟYlJ&cA%8K,PSt9';AC= sgwR@SNJ)suUHT5˥O=! FeIO$:^Bh7_|qZ"1BԀ]#N% ~D; ucM8:Լ]h'`3y¹xlI0߁pA%7swGZ]Plb7na ʡ́QÉ ^ӕQ1hYSoj9@C_["*׫36M*6Y.#500,28ي!]>X4-ؖ>1:_ٔ&8H?D oyKfrH3bX!o1g%eLhO Pv8”ك?"! j9j/L ec&&mh+_.2砖ZV["6glrMDaE59ZldK5_#=^b؛-(WE qm5(˘L'zrO/moLbls B;]N- _۶!?"5n ZT ִ~"t됧|| B"]0(;tڣ#P 5i 3Tk,|S .;-Xt:|c8R~1H@abϏ ꊯcq"i#Re%&Pu 01󑾠c*)<ۃ/r $_K%v0Nxjf '~# CLEbi[6M~3!c=;± I5*Ҹ\dQ*r")r 1Kj[|yĤ}IʃalSߦ5iu, [v7ixUTl?Q,|7qg[PUmc9ʗwpI[Z2L}yD=qf%Aİc?}fFh5=m9A(@!&T!56> *$)8*B U=1:MvBrG:&ԓغ% tO6ٗlj#Xl 8i6Pluu O6S5'Pd\ U=i!m>?k6maj-x8n01ɓ/WڪS[cCe OؒCuNmu>rBt˕',zw,T0߁-Vnv|~gN[Z]`엊4(9^45[,q;;BsҢ^ m?Bä0v0qO}!aIo\`jQUgm^G$x!~"uǾCo0m"n@ݮ7a{].4-b!  ߧ   Gdl2|,5y6AҶ2Ӏ0[bKS) !&T.۩@ʜV6yjlM5ǭ]H Xz]_u7te'iᚒjt::^2 iHOk;v,SMiU(+ hY 5vFG&cl !<8HVk oo,c&!P_ h}}3j&rx% ኿r!l`s/gP;@# |A_! e2{ajB@! 1~! B@b@셩B@! (wĀTB@!Pf- ! BZoPB@! @! ^+B@rG@ hA_! e2{ajB@! 1~! B@b@셩B@! (wĀTB@!Pf- ! BZoPB@! @! ^+B@rG@ hA_! e2{ajB@! 1~! B@b@셩B@! (wĀTB@!Pf- ! BZoPB@! @! ^+B@rG@ hA_! e2{ajB@! 1~! B@s{nv|~UײU+k睷%W^qˮ[mu jwq?~NI&}d7r[UZ1c`7?}[Mծq2n˝wv7.Yx:bBkmV y{h؏?v_|i^mY`;) {ŗZʭ۶m1Sd(f"߯#s~S|nx j8L|mǸn{c# ݺn1-_}}cu=nuUwC~-ڼyAy _A'ιkb.[l>?¾GUR/wBk1^\alf׾Zt;o 7C5i,}6NcNzˍnw 7g/ڧ|a]>1ϼfx}n ֿnzp+|.qǹo)!7~z葳-:ŘΡ81@o_t~R2t.[ ]~p1fjӁf[coq\״`e<&>=<%mjs}Z%}rsWw]wc v{o⪫f\s_5 ,𢋦u"j of>Z]jM",Dds0 &!mC"q]<;K\4u_w Za34:wEqM]G=H5`ltZi_a9~ >6-֢ok+HRyL:յVE'Lp/;֭lv1C?_ ;~OzwRt,2>C_,w/Ǎs[ZE 󾝴7&g|;[|GH7;Q'HC_,7fzM$T owMI>ӇV\e?6U~祿 |7zh8/%rnĸw834 lwA|6ˁYlgۙ1cb>8?VsV>dq>οCw|G~~8Ͱкkŷ52aT_⦚N,VG}m[m̥~c7q;n]UWvoYrtMӍm@|:\|m]f.O7W^\s yVl6?2 giOG>}z,%w6Iyf5\?웅r 7w4?~~mɒv(6 DC IۦMSU?ٔIȑm2͟7n#JЧOFW>,eN' .ZY3jRO{_;6e+ȔqVZ)eTeIҽ/j6[Y=2#l@3^t:u->ԥwߝN^{fꯍGyaOfHM>SәS.F}F:ogjBwțo4Ojٺnrk}iCvm[e[ʸoK.:k<$|Գz嗌q/xGupkt]yoLMw_?gόq8ߪS'l@}BI㘳N0/76:K3|)Mh>$ߘ /6^~9x=ok 7u2+;78鍊2ܫ|;θmwķ{]7$owN-ٲ?{71]N_̸[pۧޟ2%|γw5{lot[y[۲M_#Qgn{1˽F/74o.dm|~ȟO?w՗AߦlØ9lQnSǾ!&d-/|k[7{ۦy?Ǐ'g_}-O=1ބ24#됆#3l5*]FR-7+?>䋏;*uRϿ0}=~q<'N>-ۤy/Ro.}/usS@:winZa?J_pċ67A>;gf+V\5wlde7H8}|rFއO_ד)ln1,poqOw)&]ϳ?s|1<`smK7ȤG#&)3ȝnjSWLZ}&oR&U#ݣwz_sAw|+ucG =_"ibHޒ1:d.$. 3n.Nwc/]kio ՋQ+F:1d# 뇬/*}!_e^8,H}Q->xp\\k 41H٢ԝo2fob/g }b jm1x4Iʷ{vӌ#m\/aڤF&ߥ[8h#/}'zJS&gl0tpҲIώ6sA6B2|ߙ:vKjd}y=neqKcګo vc./4~ۻƷF6~/iGϯvM{ su`f7s7ΔaDiC|z@^)lBnMڅi#q [Cܼ,OG\4MMһ4*yȿV\K/w˯k okuqUͽԃ&Txc} ^~>'-L}bempM3MHGߠO #[![2gP/0WQI27 h}!AܿAh.JM[텈{$}f~PhۉN2z셳-6gB;#›-9xit±]ݜ&)mb 1 Դu+l&&3^2h&9O-__~_E a{LkyȁK󯰼-ߦ ( yPBkV:}bx Tk\~If 8+_}I 6%tYH/[c\%Tj)q`tl6;=:'۠lm&אz&lřW-THpQjL3N?HzQX-ex~&q/`h\Z:}#"E΀~r B/&}hS&%lɲ2ᝏ1'2ਛO$,r^P\yH`@=m2H73G oyW["& φ{ $TŶ6Я2,{wÄ;I]MLrqiTpd!#%L}c{SWF0PqHĨ~y/7>݇:k|B hSPײOP> }!0¹Bhэ3tT){[aK4_kfeU ofJOa4 ˛ot~!7=Sc4Im?OkhK008CP6G RO LeJ_VU7RA'ۘ+*gcYLǞsق&wuny{sFt]"݁ CO<%lKpAz#9 ޹FP4OBE}Ӓ40ѳxx$Ҽ}౎Z )v ŕo\|#JH7{Qށ9xyzT꼪~WlӚGLs1 LCʛ?¤؉m,8ׅJbm8xARPf|c̹NkATXtX~ytA= n5#!5B;B )|ͪ 6!͜XcusʘOݟ&Ռ/.*" EڵoD'B@-ϽǥtAE5I<r@`})GOs'Vx i0~o0x6#a8ԌTF] $xLA兑\Mb Bqq^VH 1&_Lx☑KcPC~ggDĥ*†솒τg2-e b܃4 xe{QTRܐ68c}Hbj|H7Q|QE㴆SȖf; +3e&'ɲ ͖1k 0@mϢd; 8j bpm٪&*?-dZF,_76&bLv1̳?|ny% ,/j}l ';{Loif1Qroâ2LlIaBaiexWLj& 2\.-9?穯TsKǼt @p-X' BifG?S4'|)x`c=c 1qûL=2Y!>TݥΗ\x4]-v?63\LhYb}:;]wc{kk{ L%^P3p٦pR$bg9=P'1OFV?v6=7{x^E [x r1]ZVx0&'-`lޮAU", Ke-R@>{ `3DTqxg#ԍ_lHLT[s @Ŏ+908xV&`5 &juveD3r?a=wB}iDȃd0} Y>Q6' M^%jC4fO9xs-^RuI$ӱi4%&% nMaA_! QjBp Cw; w^਎q$|ȝ $_9&Ѓ߄Wo6ʨհx=%Pe,xY05 !cTHoq#ا6#7!>4#fqE2,( }<' ll#dRDaG! dž>̄v06l W>C~6k6şrө\Y<''X"36>3ϾI [1ӆ ꥸ?wLkٵVva@Aƌ2ݤ#&3|# aX839\z? CtT%AuFľ&X.p.b@d 13+# }waRc@<|: at f,Lzb qAc6a9ebLB 07x OԡgY8]fCdĐߝL*~&L>N?ɧli6O9sZ-F"[b@2 ،kg:k#TP1(e XbQQLw{gU(Zcb52nǘVM1dWWhJb)f@ P{\VwՅiAÎXS!sfOM8+G,]J8m oC UA1#M@ay?ecYW>s4v,:<8 cc]Ek\q[b#l28auo}z\Hu쭾w:4RKY6r@=c!3>>l Tkڇ"dԚW]u}nGlf 5Yf#ԩB jyWc}&w$ɷ @1A|Ra eQ6NټC\G ЪN:QΘ)'oHCws;QGlW9$HPNeLr ?FḦp_NsLNkWlni?1u|[q96xH YD&ۇ'Subg0b52,'1]¢|pE3G"km5˽%.ٳӖyCy}M|}c!Ԡ$e Ɏxde˪ʀMI^Ub3d'@+W5,EZmS~Rh;yW~)߾-?jW]`j[zd`,1͚&lmQiKUIr;)m,wk!yV:pVwUve׉_M1Bk(О`BPz!PpCTw_({A#9 (u l;{;دi&I"! fĀjc'`Y&)h8~ZWןz%t n3[d޺j&e,}Ԝ¾ c˞^[2f?D]$f' up]'$w}Ԃ Cώ`і>b6 B@6u32D5 Wrle[ʺM !P:,@H! B@1Z5 ! B@%uBbW!<5 Rq#l>eX*k#31שTu窇/kqSo MGTv=*3W^񻒅 }!x/@WeM0g van|1;/65]ǘr[;nJʀq7 qu?'WuuD{I vk*[(;ۑo޴?> sZr"Kӿ 5sSOhSĶ֜~twYDQb\-j;KmvG+Iݥ[(ƶ NC'6n[kxlRwpz4 2hynX0kO:<Va.ތbWudcZömH6D|G~׫Ӻ]!Ӷ)?4ԿwRZTү5L%B4Q[€ݐ_;B"̗0RRo oҶ$mO7ԞyؠAna 91?ޏяh'Ы7z߷Ц|Lxͤ>ϮE[7=8ݭ;uJoOyL>x&ƻV1~c2. &}~ >Y6TӇI(;lO6~{(M֍p?Vأ}g3V1'`Qc$ o[d%Xcw9wϞqPF}[/wK &@2'AbnlcW|ẜ[i_x|qnɷe&hȐ!u۴I}j {RXl@y7n;l(wĉֻ햾Z,L /L菔[R72U[*SO1ǤlI22ejB ^=:SmOo;w]|qNxc:]eҽ{|%s>}߳>y[i)۾Pw?S)| K6=(^HS<}Y];.gYw{Q=7sر]VZɿ[?]F>B9ƌ7t~L:M|CO5sh#m|sOnzC#K{zh꣩S;sjɖ-S-4|ζ{ tg%ׯz_0.{VӇs3ɹ-}vqWPOJ_1uҥ~VϞדmxn>>K>?E}*<1gZU+yo|MMweKnB;Bin9}ϻt;C{~AK%gLqnI~׶mj}sRمaBZzWM29$&jm.9$w;z5sx5RV/j"FXs` !}EziyG!I3mqW5&&ɸgO/zۼa2Orڅ]ώ7ZmLH?7Ku'C؄FXޱge+s$D8eLFtǟH̢?<'sE1nI4^ Iٰ7vxscwդĺ&{ᬕ$pΘ`}8nΏ38r{G̱u!ƥ|;+N=KރloXvﯴɼ?\i^6;[[IB$HR7du]jNCH暬!1<ڃ~Xk+vi@?<&;zn)Ƶgbήyag̠i̷i,@V?5cZƅrn5p'@;ڶEb~=_E .(rkk׭/=kq|01lMZ_E9/lk/Km3ݝnmS T]"=foҘig W7\ʹЉ qnI4,;f`?Y)Ԝ626Э;㽺aS/?$E4]b}+껝wW{Q5":Oq1rLd&2PMZ⮷JiQSؙTh*Gp%PrxʘL(ܧm 0`Z̤'nMOuՎAXb+5fks48}QLځi_ț8ΘL5v1Ҽ'3&M1R(H," $G1c麠;G Lqϼ7E{F&) 6)k \lʳ|N2i?X|Bfʒ }'bZmgA}m`nEa,3Ebxiǘ=m$Ymɐcewd55-[ 6@HY{~å[m_x\6jgC^ RSX9&$(4~aL%j *0 cQ6 U^27ùe A9L1&52 CUt$&&*b@Ŵ/3gm_&sM:s׮ im\դALV&m @;'٧f\)~$#!b†˶ɖ?Y^m꽣ʆ.#Hc)h 3HRo58A≤h9@w5[_70I9ij!}m™ N3Hzچi^2H<$…bim2M߅r7Az13W}սPN?<sNHk]-Y,acÒ>m5޵f 'kc)Ƙ\mOsK66s՗KVkVP'g I<#u V |Yj \#Y벋JTOa3PH:x6Wܯ.V$&UQ1LVHC0. (`-fCFϻm{3qo`EC,DNEjU{;cQsRn9(>B`~X{m7o/]VZK7R/>-dt|o3Q+χ3 }똒BЧe#ϡ1 ւIB^Th&xh3!bJQmo/IDF51Vf{u9N }/hmXn3 o&R1ԓ6$7;s4t~&`Oj..?Ãz6l' F8)b@qꁰ1FzZ 墚{ݜP6&)`29 5Byj6GJ:SOƬ/ĥݻ{-Hdϊ FAs=b0+X 1H-{#ULΓll 0_!Ć'&BCG"jdh y=qL:cvK0[ eT$g@[>! +V JJxO\ʨC9;g:#7(i/Bۏ)ͩfm<7NNܐO3y%IIi ]lolXVp8WB0i(ikv[*A Cp@I)X?,6iw1DJO& F0ba8P1+lIE2`3ֹG $$Txk;Pu9#9_,1QL1eFhPR݃-_xG5w{KJy9(&f*G1&$-N}q(|g̙)vro`ٯ0pb)*3k@0ƈٞ:cjߟc$#[UbAZo?3?s<'{M]wB9{ƱU,6<7誘P^u"wS^(QL@ⱒh:wXHqn]!3?-_&0R[osKU6u.B=n=̱!eF1V&5@5 x!3=iޗ-,!D@x@(g jYGzVo}m?B yk'{D +z){\mc5K.@G ^+a!dOd Lcy^R h TvZضm &Wf68@m}yuZOa@$Y\ʴ M/Bli!3IBg'A `6=*X>ǵ}<^g6 C,v7T' fء!eդǙ# eX[mq)_9RlWQh`_ȷۢ0T 61faƈg3cΘ;_3t54l cz=K`ڒmiwA|»dQcy@G u6 qQϝgW܃pƐRP!ɳE=i7J: ;C-B@QzWƍKt*43<Ãe\W/C`cRfseT)0'h`ɴ~ؼ}(Tb)ɪ4uԙgf3Rm&p~.QVSOs*eFF/[\K &89y w,.o[磏ʎ+&5(z&9uTZ>?m?'ѳ˘\xmaiRz'7C7 1׵B o73D2ӉFȧ9Gq z+1#[ z-63)3`-=|ǼW?<> Ghla D=:Kyp f0@㢅K跴gp>imc|dS8^Nm']|/>O>Ic. e ";\f!c̎9ô2롌:.@֟||ẚ[m_r[B{Xl F-MCM2դٱPB8ؤkؗvŴ+W=>[\骺^CbMVU> ~Iq4e[Y9H|jx>#慍slhpAص~T_ i6lU*۳Tv$Ƃ}<,bR1DM^{LE8ȧN>een&%==$C1F23Ŭ!-+ì&斚n_}[*ñ״ R;;B%U(=ㆢZ,4^s&7D_Dη,]d̚ljY"Z]rU&0:{%b{JZI8_b)WHu TV4}oUj6ֳvK&ۜSM;%-gLRsK$ ('g`"zG'p/`NIVpW؀ ބ>Umu[fڣzgoanOoHem-B@!  :d=B@! b@P[B@!0 PoPW-fDB@!  zÀ7s7/lhɄB@! \a@.B@! fĀYO)B@z@l 4^sxí*.d߾?iV]g&iԩa9;.vmXzdB@! (1%g@SW۶wqۉI =zW]uG{}q__`g?Ie~_uڴIggbɓ]sq;콷{;]/ 4NB@!PwT?ݱcI)n Fo~ vo?ᵏ<5u~v=1I=;=l@_추LB@! !ovsXe Yiab>IgK| Zƀ1My߯- ! B@%Wsn>$Uko~ekLۀFTB@! @~ԩ4&Vjw?[ڎw'|^-T$B@!P?hPlc!98M{jB@! 44΅]'B@! 4|VB@! ȆlB@! @! ֠UB@!  ]B@! 5 f@ Zk B@! (R*ر[iՋKyB@! 'Mr -POT0ںuk78v ! B@̞>yo]+.P*mҤq\Sh]J/B@!@snwvRQF WPH h"&ƎYP, K-He(`$,mXw|rMt}zųgOOn:Ņ~采>=ξ4/@  @9?߼9>zͽ{ǵk.p^> @ {@/L Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC ЌERIDAT! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@XdC Ќ! @E@.J6 @("@Xϙ//rIENDB`gridtext/man/figures/README-unnamed-chunk-8-1.png0000644000176200001440000011013013760022324021017 0ustar liggesusersPNG  IHDRz4iCCPkCGColorSpaceGenericRGB8U]hU>sg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U\ 8eXIfMM*i_@IDATxtTU'@ $-{#"EDA tATP)b* {IyqlnWn<ޙqd!$I@@@@4@    &)n @@@@(@@@@$ō @@2HR@7^        IJh@@@@RXKɓ'tE Hk} `5)SRL(O<"jp@#TKs7l@_GwKGa ip?6w( ֵ+ cJ>{  zޘi4z,byxxzT;%; J nwfw:wЩm[^x>{jYvޫ׋͘Aβ_uz7  0 3vh֮}hҡѴ<{V۴)Ε Dg ~Lfn,fpUoчw  `ln,vJ ˔/O;Q3ںz5ta4Ey-ϐѣyN=?^@sV{|& ʑ)kVp*5~<Ocp)}Ek/6?}Ϝ-\-N.;@ ޘ;ehgAyup.ȑ 5pDs#o^jӵ+SGM}A7oRo]t*"E JMs&ٓ)U*}jݚn񉇧'R)_fnvMuќC Θ%v44iI۶Զ[7R6iل Ԋܿs'4K:=~xߧΝi ~}& /:E⇈wtZyǔ_OoͣW;vyo[t 'A@ AYF;QZ5i'/3w.U庠n.'mP"EyPU@c)yXrTD mQ.ڰ6O=1c(<wN[zi*mft?ֈI㦄8r:(e[|]<<?q.Er*OyYZ8\'3xMK~~D\vwaE'v`^`?<(] {S ^De/*3 A@&TSz'w<:h $C\#(Լw"Rôdw뵛ZI;u*ݷ/$h$g싔REpRECuk  @cxOٜݐ jCR^ '2mEy8g3gHfWEd RZ@z#XQLZR%K׮i߾M B8\$ HL[#PP5M)%[k`s:7Q,x[9cwj4-7^]VOw9~iI`$iӨ'*%8:eJ-}ߒO8  ` hiqtjŹh6CrRR"r7ߤǎ~ez{u&(/o\z~5|5MY_~IdcKb }N=uE.lu25U]?ZqZ3ʸ@!PuJYK#8FzJ-˾GZqLSDzLY>τ> &75=s.?{H~w٧!IT;ԀP 0uy'HC]{V4˜ّp\t8%%gL 7g  !8c[O"͖ץ֧X!ɌOr" hON)]4; řGdv!*CFiqڔ.}z- i  ǏIfoAuz3  @Z,֠@*rڋe㺥K-sթ"'n*_  }N C?GӬU_]vHG& )̑7/d7B^HNrz[P=cb͗ THVTo@@@Ug*ޔ,Kg^NKRS>ҳPz]ɂ߸lƤ-lOiG^-mΒߴ|9II"='B\3. Vqy.PC > ]½-~%M1EJ-5nF[>sX;/q*Yt)*%l/P/];M,*DR^$EʔIlKk)=$H& ->'ks]`ɬɄ7Te*w|GTox@@@ UcRfDJT@yٜ.\Hʔx5JV$lORD+S]A'cиqk|U, ,1*ujTE ,Q]]N!% Ub4R|TUpY*ujT\kYhszIz!jQU_xqڻyT-^=JO/įk  "{"I,.^|yYEK&)dg}>[/̐"]6Ԓzq ٴqR>.D?mb`>!Q]|?iB@@'萀$aHGlyDמ2O6ĊLuKM~'$dgpRVJjŕ E88oʉYR+T*$$O?\0dDf۱Q Є XJ=gKP*sԒLYr_3;YMV; vJ% Psy&kPgUo Piy{nڮ[ ~oE}]U巋Q\A7oF>3ݾM57~%uzmKd^ྭ~[  j@+N ֚٧GFEr9+'6I|^ʥs=^I˥(6ÕmE3jo$6>}=+}ƌi-ۀѣ{dwV:U\b 88AK2޳sln@s*=) &""q&Iu_y e g˿]>ݿs:>zۜinۇ>{ebv)ݢ$2W] M$   Pcy"E T6m6w?d\kM/KiL|2ҊS{JB)ClV]C @ `TwC0*np׸&K_CqA Uc`%XKIH֒}# eVR{^xHIr7'N` ^'3# vܿ{S6^}3 uUL8^ ` ^@@@@p0j!էCZԶ%KRhh(yxx`X   4uZ(x\(A5CQJhΔ)y.\U0 Q]3@'> 8֊ǍoANͫWNFkh]ǖ+WVTFO'vw( (csip!'k}p+iڲlY޺hO4(-$a*DǏ?={R֜9iӥKCYw3gjs"w=J:N7;qΝKzL4oNmvz-Z$Q>@zp0̀:(G"πH+TPLi=Я?K.{d? [xk¯-*c?lF;;f|LTox@@@rXaTy5G.YBOBBb9z"l_Bb]wW/\}[-[,û_,ڲ|Eiȑ¢D9A@tC nL!R.HAܡvVk@sfڴvw2eLknߦA~ݫmF{9҅r6kaGõNOmyiZQ]~>u`:cd:j̀?/R!C]R6x.| qu,StreD*h fq9nXs{&xiRS.^V_2~8~eཝֈ|5jjGq#rܐi,X5hRˊwSF\j;.R_K[V.?>p @@@mU{to}k/ģ\3s6oxy#S'zM*ԉjDD[J3RVQ]~u UgT wiؐ.=Ie7?@d[kEZ㥔+*rL~3gnrg؏Fr(;7ԁP 8sV…>Ye3I?mcƐtzEU]a@nF}$]Dz#<.\Rj\.HRA A@I ӧ_J$LC?w.M5_ψt{^ ]⽳R^+Ox@@@_PښǏQի|'4[J?-:v [*=TDhϹ>j3g|4kvQ"^p( ;$Ɇx/޵anڢc.%9⒳Kj[s~9=,4櫮@@@ Mrx!K`͐"4{d[oT7qo03x iz_8k-D;lԨʔ5'MAx` MO0ƃKy Eo^%Xw.CwUEjYXa'|Ϟ>2w. k+hp.TGrDJ I wcLmWŜ;rBXٲE΃Oc+$ {9w;L H2wq$\:s ,vkçMj P"E!C1W H_MչiVB~C  JP%)iwX9f=i~6?lUF6Ҥ-~nc ̄IKjnNK.ZD'ъ~,ٳSAm&=xg>^v\NkC8eȔ.>O;AAQYr'OFc['PP5MVWY=9vmH!ឋ;t_6'q\$Y-;wzHRrcϞZu۶Q2Ouv9@@ %x%IM#g:e8OlgjXʟFGpQ9b3PP5 V@qijɥxl ޖ0uBJ6ک3πAYjSZ,$5Ԡ/ vIFJ>=QG%=ow!ϟSDDU(VLO!R%@@ A M8OuN42/dE}F )o?!p@@8g, ^2Ql?7'#Y#G  ` @1bԒi:>fȠK;ʨ/ΩR]S@9 ^Wa,cv]KO)N/ÍPʥIi&t;.2fnݨ;ӐzY3ڼr%]9nR{088HN!=%޺qrڳiݼY;rldd\p@@@`l@ΓǏKz2 P QMȞ;75jڨ/; K@uIH} Oxlo[&nP(͌=tq͝PJ7^Si6_-] ع3M:fbDuςP!,sԼD =;TLBr^޽~ʗ RE){EN>X|zICc~I;jHA_R=mw͛5CNщHR]=_p%uѶWG̀"xs?D.)t֭hYOyFjzλ/^Ѝ+Wօ\3Ց~̀:@@ M1Н @MIף 9Jݹd';cl,M2شbϳ}`A*Xv$I{͛iʕ$5Seϧ%ywH{Dug@@OHY 9ɝf~5U_AZ&e}rR࣎Z^77͂W]E8   `@ 1tPqt#CԦK-W> O"f:e }a & @@@q1U-;u_֭;k۷'NDGWoL cn.fՖ$S|/,K~[}뷕 PPuNySJZ;wBrEĉܾz" 3F;o!mLoF>|5~k|=   $$c.͐DΞ9Sv\64@]BӦYT֥?1{⻠|5 `TӵSӱ}GEJsE&t혝]>wq2f$ޢi@P)& `㧜Sfg,ӦOPdd$95w( (,nH`+K5W{lIxo H2Ο8AK1Su69A@"TR~jF_>w_L;֮~g˝JW}qXZ%]6PgnY1iO*||}1/[[uV9@@$TaS;wjVWp0iŊ(#^:ܿsdؕD?+NJ|~[  j@x)e[4{pOOڹ~;j҄<@bڹ[7o*[bؾ}q /K=O[EuA@"TRZu."3wwINIK˲wɐ)Jݬ-ZMǏ086=~j4j횵?To@@@=hũޘ9bG\O4{wʕ??UULmuyvoH<TYrv% ^ vMc:5R*~NLzpڕ]XTo4ZqwPf@CzЬ];k/F8lߣIR"}O;]ԡgOwH"loLCNщ薤~   `H5&)G̀{nՊ,cxVQVp&|ui?P4igOxn\Bw@y)kHuu@@/dcPvm~QYMs?gNd 5|5J5''rϚS%f$Ky;菀k_`x8 i3tee)~`I8|ʀw3 _~t@@EK]X%xa9OI"x\sA\74?2?N͝Kn 7 VըukĈ}9 | U P.redԼ}.p YϛGky[.$Zp6\-l*b~qh0P It=gs#ieIOЉxN1#};jhH> ж5k(sȳ TՏ!%  HBPϐ#{hNI߹evBK1-\~1߽eǿ]](N Q~Gq%4y.RW/~hܠAK:UEDIֲ_M)>b'w<   O@9.i)&}|uKY8f&R @V-\H9Y+b%&8]+@Jľ,V1>N?=9jѱ#UoЀO J JAɓ);DJ3B@:護KhQɕ^Ye3aH @@tKnF}$fС΂nL{$Uv;mZ:asF!gM\ z@@G0L}nY^vK93&6O{/sf*Q<֥'w<   o@=>J[Jک>$/LΟ}h_dsIH3{9*Tן0A  w@>B ۷k[ۿ¸b1#IJpyn&5GKڸ|9UbNM\j˪U.?Qa@ۡQ߰n]9nrxfw:Z:w6[hܘG; "~C  J@0i_TF 5=LӾrrE{h]7= !πAYjVi֌&3;Pҥ\}6nV̝??(VN<[AgTן{   8̀*>0_Mexm_|tJQN={d~[  j@x)e+r:rƦՂ۶iv[GߧZMRky_Dx&VQ]~` ^RZ)talNJc_ܙNJ֥sǏk:f/Ǝ%7FtjYi<  J FJt2D3׸e0\phʏA\p!yMK-USGs-ZfsPAj ܸصs]?@ ʔ/O;Yt& @w^.R^{]˄7{Q-CއĊYSx` MOT1T҃ٳ_~!iI.J(=x-:ɌwD)JU ~PC NHLjoomwtAVܵqSZA@@@'T'aD3;wb&?Ÿ~8a)t5s͛[ʑ7/7x0u|D!Q]  %TCa|f ﰍHBwFFTxqiY)V?dҤXKּJu{@@$Tq3ŌG~0Ad>*YBG%q+4wj֮UmC:c OthQX.YR9kt>ڶz5}0dHStKP>.YBTuA@MGidΞ;x*)S$ *ݐl/P/XktʴdlՍ+W j׶x=7\PPP7<{$hC0Jw|7^ni!uL2HҞJGy0y mX5n%&5jݚLֈI7Zmo'uE9XsN|UY&gy*Uh+_>i~ģ NMr|1gu9/dNA@@}ƃH^>k6#q##O.y>_"q^3~s_@@Xk<՛sGeg̒E؆'$9mGt mI^tYF:< @\PW p%ʗTJ&[>F~tP2z*>~N8IXٲTLK<  J@\5wN ^.{l=NO>>ܮTu15 U{ e}qe\Emy $2j 1N`B3g߯Eu}  ":X"x{eU(P   @u30ĕQ-y(,,᮫@@@@WP^Wác-D3ǎQfxg؁ヒ4iB)KR*)R+T*7i;O{@Czp!f`-Z18S'm/+ߧp:{7ۺ5&ߒwĈ~Q]~> Uo̔N` ۬u׏KZǍX;aY@r~ > (Akբ'ԒT]ϩ?Aq*;t7p~+&3%ٸS\W]S@)n PӐ2+Ӣ{)ot_7oNzdlJ*u- _u}  "T_a8kN r(Ae;9a,@IDAT5Qe]T$kĉ`,3&iЧ|^Ν8A!볽Y72L4@'$$P xSٸM5sݬ];ZghՂ.Jw(V,[,^W]Ep@@bDjn993weҷ/hPzke$/;STL6   &/C='{@Ӓ,^CS>e0(X=Uj 'xoֳ' JгW:t|*_  ꌕҖJ[cɗ;)UJ/%L)%_di3kΜZ&8e UxT1}ՀPsHc8#>O]߇ŪAׯ9G6ٍ?3PP5IY+D\lޒ۲2J.E)kVK\Z__eȘqoU'A˖ @@&Bj"_f;٤ K=&Si`.>wo]\/[ɣuKr wHM.7ғǏ+MZzx^` ;ES M+VPxx8IHB[wo߁( @@ `4$HJݺтO￯}1[u1s./m6c=/ҧݺi|Ɋ ~C  Jä}oIG˒%껫|iuPʂϕ??ux=.*1LjYvmûvќ)Sw%oG2eRDX-|Z(ҟ"E I.u+(W?!p@@@]n,˝A py͛IRf$${B! YSx` MOT1ԭ)S_7m"\4e5kM^[` ` L]V$<~sb EsICFoj*ww*%֯O$Pu>/8_ga%ֈWﭪ9\0uFZ?iۖp؟cӊs5q$ɛj#Gϝ;\jԒysڤMbc  %Ըc]:s&;ϟJW/\ cPb N@Is.^fM-+_   @ܐcpMI@'$sIH MH;F+,Hd / $;ӗ})3PmS]8u*e~ݶ#CX}npމs   /$ YR1]ժt̛$uڨcJE5h`dAѾ(XEљhɲGm=65 Uc` |l1dUdmEҴkjݥ nR9^x)hԜRyբ]+Lk7$!{|` H>ÇSƍioӧ]jEϧ:͚%{5ӺЏ}љQ_QijSNMJ=ζ.*1L0QZ_h.ښF;iJϥsLKH/^o&FW??ev~u` ^RR)H?} [0޺j¤=C{*1n4~mLvG=9~C  J ä9[$G޼4{:3}|3oUQfc賀M{z_*WʿIMkLQz.۹>lp+ 5؀ɝ k4h+4HR$"FL$lQ*MU,fS%)&  jVJy!4inxtNo>ًOS{93ຠR>1Rh5rK{*CT\h:D  %TCa9y]k>lۖznMz \ypvvL)]\Qd>}[Fs?;LLodo:G'OC>B%BɷliȘ|3&<  @?FZuv"i_iG\ΜMDGt|kԮZ5 RK6QIoYNJ!\i̙,5= 7F] l ijPPPϞdmT~ٜ^=FiGөmnv iuB苧O#)=*1/[[enPP%M )Bo;2[P=cr;/JgfsGs,Mwtge|%"emތҕ,nx:x^K  @U=x@8Pɒ/<*U*AɃ' ֮.ЎJM:owwFtkzФ?`|0鱆s%G^^Y={}\*NuKekouu'7ڼv3't'~\PPOKpՍ W˚>}/5f9h'mZ"p`._^=i?SzyQixՒ=ϼ3<  ,3VZ*KKf֎4>>Tysjܦ }߮*ƍl|Uߵ, ,9*ujTE I + ̘qhMOz-.7go=x@@@n\"'RsaiRe}=gzB?{&ztjRѶ]_2 \h>/A ~CTնի,{~)u4<=ru|:ШGSE> i7_ %myr}ք$7gH  >ꏡ=pTjn,)K*Ը~er:8|sg㏚LzV[W)iAj`W_kZK*Y7Fj"O0,cu{oӐ Yw 0N*M2ou.$lE(UѺ@|'t5 04UOߟEH9Px3)~Pm{08X픙O9]˦¥Ji.,VƣohGJkSe>zPȹ󼤾ML0~GN΁N#GBb]O c'@@ KKZsҹsi0KV~7l򙉓c\E>O >-wĈxuQPwdnZ^NB׍yk.:ޏ%:E3jf˝A58,ULRil5^mzL~~ܩj҄^{xUʟ?릋n)_P:A^Ln.^J7W*e^դ&Ogq@@pnHPbŨ װbGҾ7:Q;T.Gp@@pnHzr\>w͟O?O@Hawp=yzi җ/ sڞ2\+ n~ټY޲jULx77RcR|9#CJ,Ue9   2qާ]; =eTTNjت5ß& aB#8P@ )exnԺ>ڹ/>}yi/S&KgΤ7jԠ>N]yj{Bc2hևg7G G?KҦӑLi't菀]Ev¢$$@eFLxrPIG"r|''{x2ׯkDs2P]I\sС<ݝ-KU90$+J.Q+_>^t0zt$ݗ 6ҕ*A)y)-o~ݩlgo=@@@@+e- k.]6iTx Ҡ`Pڕ6\jȚ;7'WR$:8-=>.8ދ  plV< ۶,I ^TK0jkBMҕvݻ;֬Iճe!=zЦPޮX Ղτ%tCםmi@.8* I:|Sr7o_̎6_L9蔂yMj5m͎J'f"Q\)szl,͍@9 | U ?GMsGsS(v_OxjiOE*͂O4βhr&  #=cvFG?L6kF | k{|c]g$Kwql>-=쬑zsW2yetΰF  Fh77._eݼY; =k뗥w|E)ް!UWϮwsnxzz:-CY_iJY5%|yC;H(%c\we:vxs+̖:tΙ9sZ~γ!9ΎG>EPMɿYJ[ܾ&3~   `@3${Ԩuk٥.M7^:!I,cGpjȓ'f YG7l'ǧϐ&N4yh9|^'t;4gu-};/X? l,K@!C tnoF7ѽ{(K4:|Rُ$$h@A3G{8ϟUڴW*eQ!mC]@a1Qp m6ϒG.^ѷ\I۶n1P^sۚ5$K'J/yIC57-[?ԲMݸ!@(c횔VMJ_L{t؀{@@$TqSj)$K"Y9[oQvNy)ۿV'>sɡmp&{B*#-˔Ok3S{n)AficGƚ]~€2&4G׊S_P,Yy[~3x0e+|Ph׍#wZ]2+;<rK+Vѳ7+J O< ZW*nU  ꌕrRHcr^hGT~&q6|^|yJglgχO._wm|qe/doi<  J F>ICҾ[׷Lm%ѣZGs}*~?@cdn*wwz<93RSmp.NAI a'tP}քӐ(p-ˮIς6l*uR(gwKP:{g̚&^PW)MծZ5}MxrL߲r%͝:B3)g#!\tgfk7E|v#p.\h}Å 1/~(溟Rt\&N1}DOݺQŚ5/[}wvg:=j IY6 ժP ΎGm=657oRcrEٻe u3V),y ^3pGF=] ?>U]EGԒ >E2_@@ GaC 9[6~| GJT<3H:y`w!] *߭6_K m6 (C2CCVB-,PZXSr)~)$-{Q=:yz63F]KRoM@@@@+Xj09h'm\xds:wo {֫g~ɦħCNɵY }6t뢵43)   ,5Aim8{*U$#- >C-|ǺutV9@q#OО6kݨN>6O>uݚζp9nrp[!܂st`(#;W΍|Ι4iL Н[ΖmR4kfdq_lm6(C2CCH?Gi*ާD#is(o]=C;*5(]DϹFtkzPQL @j!C=^sرC*VtnZzVXz=3eϦtuHxt3/|8|81"qϮI NPtD@) O: (M:""H|>tA4"B[]&̝ٙvwΝ]>=ܹX-]ɞPǾP-@pO#f~#p1OJ|m[k4}(:yo9\gرԹB}\9 缟Y ަ`ʼ4*Dq ѥU)wջ'kz#C]@@x7YP8\*mO_ؒidtKK5?ܹͣ}=@µkt'>q'lipMOZfRMs҈n6mZ_OUj0BdիZBEdg٩H(u 3C:'5zʙ+1+iAs&n MUlY)4_^u 1g  GhMyཅ;v46r[jp&yR'Xvԃ>EvEGf}DA>z$g`.)fw @:{E!_?N= &_(=ں5M9mNnn$'*ֶvyO`!%  >$W1 $s)fR3xq]ݓ͓'u>P'O:||Zr4:yTA>.90խ<H_pv/>}(SL.×Qs%6=oXnu-JgvA@ER9břɴ=TMjtWJV|Rۏ~ۯC:Fѫq'Ͽ+{85SiU4΀؅P̤q71QS9Ie޽{S`%=?O^,XSeI7Oo(fT@'*Fo  BhtS*m'QUjr}?q[Ԩ2e{7oѕ}hEyʍZ΅ m;dt@@lp@&m҄sѵ˗:uRTpa-)z?#%|?̙(bnV73Q';БRH\A@@ HD\@;O~|_K..$sEs W TNMcug/Vq#wq@@L#4X$_nH9Q('_wXIޙ\.w8O/$p;\vq=R[^5y:n=Џ 4jSq+'Wu8P9)WA69}NN{Hi]BY g{   `8 ^GjJh$gJrW 4PEKHsؿZE'vݻ'b)!ۭߐp  fG8K۴v?zarUۑ jߞ&ϙCTq~5:bƍsD޺Ъ9#?_OK#؋P{'F[7nP5k_)DfRϞtWHJrCN?Ɠf>kk>@@,OHJ? .U2q$:SGӼkP"4k˔s^:xxʚ-Dxu MA@|L CSНRrժ?a| R% !*Wj")Ki()Ο0 s(L@^Mwќ.7۶ Χ`W]u @&WJOů;z{ga@ݥ)͔`Az/$̺p~TɓlٳSmh~x>|L ۹ǽ3ν~/A@|HaByx t;V -J%ʔI~MဣK] tc 7 0FAHƸ.!>yO[8ښiF n WY~^gtsv+v}AH=x>|yH pa't뗏0Y]+lʕt& ]$藳g7tzn]ֹ3uӓfh  8͙m̕$F^<6t h} -͛>Lο=v/ޥ{&Y=tYNe4v5kV卙m@@:)'y91+l.qR|\ĔahsQݦMC?'|o֌֩闰9S_xA;7}t|pLԑT7bf7 A xT >_DEQ_OS6mh .,]" nԫys:MZg4hт^| 2SpWfTOeRj fիSk"QQf^Gs~taS:~h|nJ3 ߶{wriQ^ 4GqJu* yb(^\7=hO?i=V33*&p-fO  `+p@m5L5v䀹'PGTS%SdqvųgIozb$~.[FsMtk=?8cТ0 W^^@3{+y>N+@@ P 8-G@j]Z[ܲf h'E)B&O֢=0Gҗ([vSt^H狯8\86L e =i~/6%gTIS>X.$(xqzmԩzo/{-(C2SynmG !Ɂ+o^-HMLL6͛ ABa|{Px+qUGEnG |oW[ΥuIRH\ ilxǽ 1g)񞚿{ @@#`ϥ&WRU8O>ӧENѴ~ X۶kv"η8x5R>?s&M?gt|,?vv>GVDŮӀ֭iz陿A@%MzPw Pcq2>L#C\F \T^CyO_F9̷)QbjKuJ$N'oE*[9ۇ7gG\ t  % nO_~.. \PPУks >Rq>&*ҜƼء}O  "W"ϑ߶n/I&|W>E04ӷ/V񧞢|D nb^:e q{8FLFuUgSZIv)bn$֋-n.VJ@ ?H'IsؒfiqwQ%ݯ$v+"j~wc9jH)S5m1"GR˓?\|_cIzRߧN(4imfۓDЯ[jhJ(K֬C{o׺ je{2[!Q @@@ p@&uE>;l=X:3n~*'[UȨ(jڦ 5¹d Z\;O|$Jq=DգO}<(z.۴)}"zzQik֯h  .8+NFU(}ޱW/J-F?P]qq  IM_A^|vB56iу.k5UU+˩^T  #vSj^dz\tlRr/LX H8˿?YsV~v5j?*khĞR){l3Xv)O pD/f2_zɥǏwS'N>}-[j[l׏:^3fwy N؆W͐0غn%rNN|!z-e&pʠFfhZJIH6g`Bwɾ͚mZ,7ԉD&>=It1ф{@@ 8+b 7w[yG*Heۜ {R%hM駓9I{>疵k{p ϓyY r!pzz5=:3W)4 k\ZiHyUu;1ȑ+;wq^9Ν/!Ix?Eu^*&+Ut- 7(C2SZ RZ\x5*ٶ:ېNA\YKͤ+Gu4'Ӯ-[\:D?}Z{_%?Te~Z@@zzsbE8GϜ4 hc+s5q ~Fѯ?L8 /Y*y .9Cy#Ld>_VO<_`  `p@-3 $=ZsBժ4r4*+h/غ^S Ē-勈 #UJq'OROj$7w ҅s6=  ܔ%Xʼnj e?(sH9;Э BY޽KG^+Y""#9V`!#:q'Okp.Vf~p B;,ů倹G]j;긻0LG'8[(  Chp~]lyV>4yAYtY$0o=O ͒ť NI7}e :A@@p@=&hU hmm4wXP̄ u{62ek֐G9;=zTOB] }nO & b Z󩛞7ܺqnrB{]…z)&,m7YMgN|& IDAT<^eO;WH}e+S4[8' (`>a,8c:9^} +AEuJ@H!86T+ II;XAviW8*Ud  28*Ϟ/c]jDk-CڞOwIrXToE @h 2hIR}HN>ΌF˗TK @@@8x^#-ݏɓ)S$ "9YY&%?-[zs󚣍|i~YZ{JIύG9r]6   WFk  tJ@ (z.?)Gj6hx΢z2]xJ-m!!!>K~O ;wPy.z>\[>B֭g>R S~n;FNȨ(Qd8E?K8Jd48=x~@HLL s0 ~+0"8[~$p*&j(F:yZlK.8N'l߳'['@@@=p@՛3(V@HhV]GDil_ӐN(Q3(y-O0=ԟT#؋P{'Fcq۝;rɔ)sf:UŽ~?{-XPN q7_?vڵ.w 2@Y)kldgFkA@O# @$W-YB =C&@ HsHd]I͡&QO˖c*Sr<^RH|A ..*WJ'T]t &͓aiU' @@@}XU1+*a(P@$|Ӥ-|@ܱ#j"Þ))8?I  @@N+V!j p@@@@ 3} @bb"]8wNDTPs@NPt=I<  'S 9P*Y7pڣ1xKq?X@…۝;-R@@@\ ]g@@@@L$=&E Oz7ox"6#m`뉂"pC@)5-zpC(8    `8E    nu@@@@#<@@@@ 8ny=! 0Pآg7S    j[     P7Pp @@@@<p@cA@@@ NGyl̙3t)eC8 d y. uvA@#T ܂ Rx\tE<t 9BeJNAWGił~}.ܾ},]JM6uw@@$TIaC̉kxש^zj\P@fBԭ[ZjEUrm*>sg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U\ 8eXIfMM*i@B0w@IDATx}\ufԌXJZ-{)NLN&/L_ĉNlǰ񮗥hg?֫^h[Ru^ϫӷnU5LN!  @sU]B@! # /B@!p5#WB@ }B@C@:,B@; B@4"@ a! B@H! BjG ! "@!  P=ruX! w@! h8DB@! B@! @! p\B@! DB@! {B@! B@!p5#WB@ }B@C@:,B@; B@4"@ a! B@H! BjG ! "@!  P=ruX! w@! h8DB@! B@! @! p\B@! DB@! {B@! B@!p5#WB@ }B@C@:,B@; B@4"@ a! B@H! BjG ! "@!  P=ruX! w@! h8DB@! B@! @! p\B@! DB@! {B@! B@!p5#WB@ }B@C@:,B@; B@4"@ a! B@H! BjG ! "@!  P=ruX! w@! h8ZWR)'yOLijj&~+ø#'Pu\+"9& qޗ"B /X嗋+\eZsK67[ mY --֜VAbۡl J`Q"@C Y@$$`D2{ Rb` Tٙ9y-ҒItEkji2E%ep"$%'0-koo.|ڭ?$yiF'/M_#! 62Hn=V058 2$9$; 6=3gzfږ "b mH, BFÉ|BsdB T0뫉 K2-P# O ޾^鱾6hڨ)B%[$qo !p! t=SHl*PC?mvndg֦glfzqKjƥIHZ顖SY$-d16 uw(Dp-YW5 d ^y 3Rmut|"oH!  DǨNCd'Y׼@MPBv M U6 "б2 'db<{E>w+OHkȜ\Q3"с|r`cZV"0EG!A^h1F{ $e) е$ѶfSZ3??n KnSCcE%"[vc% 0d!I4%9}*̃$?IHZ95ި!* PZiopmA-C!" D(hB]A j9Hla?3i-NoCCV˰YZYA.K{( Z yB(i(dFOHfBYHd!i>d$fJn\Y !Dkoo-[ذ7HNkkIj!PAm&&k|ZR =`eP ZĴ#/ʚPaV'3LR0I*͇Mk: Sc*knCoxˆX?lhlM2qrB@\]G gl|C3 ly0•Z& =Tg `T $#Ғ1J! {1F&;Tk'y4=i#)%1OW &~ܾFG 7rCjX@rB@\]OFr  @{΃:yf=\%U;+cR,ysEclP(LtH-)F9SGy@ȓ ʼ9biY/ˇi Q+=@Z>CcVۻCI6B$5C{LNM'`3g B߅IGzt$e#F"+i&$y@+:b\ZZ01 I*+ p̟_Z5gﲒI3ȓ4OT Q-%\FN۳gX/SSfrB@\}]}D ME`+‰8y0ݘ 0h3wX< ScB\Hȧ{IJh+ dYjdMi 2~| Jԟ&I(McA ߃?&6ck87kLԴPODO""K#,DDGn޵o۶m37UbJNV#@{Y; c+\ ɠ1\#BdrxFvɃ/2́CBYՔXDOw!m>jA mS= QeTqxd`4$X mlQÃSAhaڟ.hF::}!nnu`/.񼭍$ $6y,!kmyCo~ 0r*&! IZ!P+8CǏ\~ƢLC"#s 'ia昞$)19 ;rd{Ki#ѹr]eXZC/B+433fOLNYDm S86<hF:8z@tHxlƎ[iO+Ć]6b%'" v!P ?'NGbZ'pk'R>á'!*r(YY̋dpuܐ4CӃmF`Bp"*+թӧԩ8MV,RW c %c |Lާ>D8!m0Nj~Μ9ky¦ffmGZpo"ßy&9|sW--Xh8=$P3ڱN;x`}>6cIq݄@D6LU%j84?njS3KXAGr2G0'$_KyHt< \oƇ6.=]=kv2)<7$-z}!֢ ]H\e?fHv<#659c3s3M#@ )<@KFDmRq֞m,Dy p:ĉS0|>CMg}yw$4[Dx\@R I[$FI1܍9hs_6h{:mhhmYF`ۆ^Hj(G@m =mЃ=y[t+kNT0M@{kHР{Sp~'"A 5RgkPV3B`)hΜ=gI .HqM2zBHk/,,[Kp& nE0dgmۺ>؏g .(z$zTY%iU=N')Qܜ1jwi,#ۑ'lXD%T[X{N{3f[R Z>(Ru!кR*$@ą&8}v̦1 @K#yog$~Z4>9g[I}8<ʸp"VàRy*V6Nm y9HF.:$.m'Nsư|Xۂ#;Й?tqp\"ԆL;[ ֲ<頥 dfV=?N>g2Q,l u]M< Dּ!b3E;O<>mozߊݼg94`Ou؞F;5{={vƏ=G@-stj̫q\օcA6G"6$;tozz [rkδ vröX:GVyC q#V%ڵݸa$K|lq|`Дst ?eMjx8f@prbrVIΌiŬ+`B2CK3d. sBS+Ф}q{'Ywًfw4d<ĕJ҇%0:u GM5s[gQ1t/ rB@\1"@W *IFpNLLc l0sNoR>D@Y Fxfv -DX^ GOvY\e W9OqĒْylC+i3̍>6eLKnϽsSc}o9 MbΞ;әNNyAtq 6Dz݄@#" ԈO]}@8=3cS-n[m߾=X5˱SqP%NU7O>='MGߪ;įJ(´tN VVYYc/?`w6?,~і> ۹f zl켝8~ҵ@==Nzb4I$>\_܊ q׎ml۹\ 934K_yLCI\d%{VaB^/1X?v4 S7<nzA< 6Bl|yI0OBr UTh1n@Xq 0r2/1 Ry#`.m6eȆhؤUa'AdQӟ􄌘LJR+Č^)Lmrs|{~MxӒm߾նB֍-=M@ KELe-X7"@N@mXƆ03By_ "8,Ͻ!`"yOF? ۾m 0Ld6QLiv?[=ש,!b8sY#zSV,k|^ǡZv-Wt^n|0|vQ9"1={kk<'KDǪN]OpcfvF Inau$[4 S^J$I B$> 1MNQ~!d\ݾJ7xxOpONYt.oΛ1. XH,bu؜çH 5jvȗH(!Z4ZtP u! .TH?qck>"rdZ7!IR$>!gX5}=CNkB|R(pDc8 4?n<Ŧ Qdy+ KO S\ivۅM&#EK~wP|B`95B`iiw\xV$hOÜNr4,=l6A.O/ϳY}UĬii߯Rvrުu )-¥c8 @ z+U5OuA;fa BgX/"@EN@ Z,,n 3> >ǣ@Wx[ dOtPDd/y;IW6H?l7i;HPvMƖMӫvC٪6r4:@8J{b ~J= 3\u 'U.u+ zʞyۨ㈌ c:+O_V"rB BU\l60 m:ャ K ^57fIr!;iLWW6wJʤKѧjѹiz$.II&;;`_Ss\9/I466l9~=XUN* zB$ORK;4@K6lmnSCi4q<HmQfUF@!S!P[8R[)0@xJ4 r!1bǔsN,`삞A2KȐ8{[^~nޅ:.Tg˾q?3g~pGN =Ve-:)<i'bK]~e!c۵BU*]!pD.ՀA CJP2 @:bԋ8=^[JR&md=}ڪdEb 7cVZ;}tuBe7\Q;qDZTSVǦ瓒4lw&! jmm-PSZY{DC 䄀2Wtet:HZ|ç~"AMt<Wp4  MCQ2aԪS^IP =dZ?ًnÕqWRpyǧ!*Pu# j= ӔQX݅<՛Vhڠk0egh ,+OpiIP4 A@EZ͕:|ZZ*A.qY'cKyӀtAi7/by PCj$ "QNPu1+ +bRN+C@Si!ajW䠘 A3ILOrx^YG[mZf{2s-G&⹬L9/xe o[ƀnTR=&u)6.T]$meFc`DMøJ 9!  2TZl:ؔZ%B@BRh1z0FoY~-(޹G,p (ԷDO\p ˋ8Di#W:aByu͞/2=03 c@)1_JN[gi1j8'TBrrS~!PcxRxh>1cmF321$C1-HB,@8bKXC"吟@P,N긂k[96Օu{K %r|IسA ;`h o6.~0~ P% u TD 醦;YA7wLCx Ґ=Q\ٹ9?iwb`0v-ks$Ӷn3\]bX;2^;V)@N \lu8Df2004 탱'fq%QAt} W<բۜJ\.1!"s"2|Ѵά*eLhcs׿OΕg ݗY7n#ܭ:c\@Iy5Tݗsϡ-6 b65=c`L}1bڵ S=jKDM\]Vݺ~`ǝM@[S"O`K|%|)%EB$KKKv o”K;['6JELIF(O`WW >>m' BX|W*2h %;s~Nb%sAjl;sbfQIչx-8f㩇?9um{xYFԋ`޽pNX&|B@"rBp9a,xB/5A>^BL"B'\۰ex Ħkh%R[4[Re|z>xa/ (eY/aJ%QP{FHII4<:+>ޝvֆ)-Nϱ'm~v.hyP !yFMoQ+PPu# nTPC 4<4C)[^1 Aɏ aT[>KsV/VuuFe3]"LYeq 0oΥ*yIs(; \8 ']x-#1$i͞|p6mxN9rS`nl]sϭ6 Ϟ=|h{f +D@ Tq!PK::;a3b㓘Zf 9oSy͞+O>!ũ"N>ܹ}6ދT5I1L3Ђۦ4?='2bcZ,S4j5 {'5BGlfz{-CGvAxϾ}7B3n6#W_|*,j@ Cox8Ӡ6%?I|#O.>7#!Sa| h/ 4hpfl=y!U8//{)_+q;$LgmW[GmM7`.U4Nކqzj 2 r؎r6ӇOriI$ҳSls @p G'cHqkcZτ:fqD{ȏ[V׽:τ+1e/w?lhTM|-c]=Øo>Sbf"Nyn1>c |U~XE T.1djZh v ۗp}g== 4AsܘSx >N~:awu|5O|B`($U!4mx$evn# V8`#IO.q\^߄f3'zm Vm1.eMھ!Wf\[fJ-n,ɣ4::z_tc 5?s=q ۶OӉiܢĺ D@1z}#@@{{ܹt.O9E:L'iCO"siOcQ6k&X璋O,'^MU%>2-7HtD6ACxYvi`+w7`=x^ęg=- G^`O.{11r*@9"@x($)z3 7첥EݎoZ");H"x '@H瀎=zx(̜@ܥ%۽k'lZ&z G5SARQFR²"?Ujsud}E(K jypuxXici{=Q{3vbu'BlcӉ}cYÃham{ku#iD ! mR ja`t3V8uW}eQIC>S)OȑЌt* 2T3>s/}h{$T*HR+_>^*$0-K!ë,w`5jO?bϾcn<s}}}1l89OU쮻o~& !ymYjaF`S$€zY,^^я"XIߒ4>0:3gR+2qd,#8%ƺkyo34 RqAX N=IJ$+lqﳛ !w /j\I 5dA??bs`:rqǭv뭷b: eB@ 5LBF %ر{)y[I8Ȇ<yOW,PĠZB+n̻ a4MDAz@GsR}:[[`3lw?.N 儀 "@YMG i1sDɽfN8+,h>yPX} ̘$p/Lޛ}iaT˸IR锘xcQZ/ƅG2>#%J,_oIc^jQ$pU\yL<%;?huڻٓ߻v,aya|Nq >14b3K;[Yo_ONUDR@=f߰fa%cm'cǀċ8cOL'sBAQ(,2V6)hvb!Ȃ)"8"gI-xzd%A۹LR46U@+KŃEFŊ|IL8PhO-'G&Α9.38 6ASnؽw؁79)[! jPM`V#Bv`pihJ=~U-m|w7#?.qH7 DBbSS8cư1wT϶#cv1[rI.pJ bi;m;(5sO=f@ZW ) Z;pa"}&=ֱllPښA:q0uO tB&f5"j%$A\~)v.0<I( mn3*wxdB_"[@VhBGb4fgqI{M8#jNbR]SAo}َ-`S(S ?ޣvnL;+ C|Y*,如C#y\궃ö 箻^[F,?=5"@@( !PcFlcس{>bDZrk,h;&XDB}`OGw]ғPt%Hjyy1h O=g4;?M" IŖ nk"?΢]m:1_q虺`sav3a6#nVIt԰˱DLM-7u`גݱnVl+\D~uuB ԼB`@gxum6}f=f1`gĂ:$ ~a\e y3H^/oT+Ig϶[adW4A\qȏp%P Md^1.& -;7UW.ũVd)!+4pBʹhO}9tuv8 vVNz# T'@knyFqᣏ]f`*bW1$%Wϕ{hӎm-)B@@xBl*#h)[1%gwlGym ]s C$$蟒D^&$ei >9$+$'& .ޓP$ۮLMa) ţBVNrY0D=| ŤQB_+I6 *@]+j\'5 B\421<~dw6ORo,Q^qHpxbua*1F+ emm~%bogډ6\,Vy!9x 0'wm V`HON# T{բ;QjA{;t)K:*+QXMC@hӠUBG Oh$M"}{k'N% TR6- e p /ӀlO`E\HL h-P5ˇy@IDATe ٺbl."kt޳Ln$S^(d;t덃p\I§eeB6g" "! (Zoۻ!9c3g д+ǸܝI>' ̑R@ B+a\Z=>ϟebb+I﬍ST\l}jNJEߣ:ɛ#?,o' Ӓ 1wR*=6iö}K݄yRG<]^! jP @VBZD nG?<p)6 ;oΞyr>Fr EL # p@P8g #Kw.Ŭ&e&>`iO]%ukṊٙdy%): υO)ť{GNٽ .Et5E@p1!pm  uww$8|15v~bA=ۊ)(#( 0"NOca͠zƊG$,Hp!9VǗ"}I{'έ62 MTYn"@5YMH!I,8U⚡- 8EiZy1== qlrr?sK mO;$)Ņi O[Hj鳡-C64oC~Hloo ف|Plwlg:hGrLX-_LX6y{{n۷A#FR !m2^\0w&T?[TN⼽c{w@І7kwU5.HEly~AwDBFzWFbֿoo{'M `{o+k+qfp#{᳑xT~%@LrD!T'/Vdr}$N쵛wbʙ_k i#"Ņ;Nl8"re{w"X_zjs//7~'nXٺ &tUVg>>s?e*y䑫Boo +UK㎫Bz Q wAد])?H=ed'Û ly-ρA6,of*A\H~byNYh^yv'qvwÇxǕTuFu{݆J t+گ4'UѠ2˒!@5|6 ja:G(' '?,YXuZZ[OecvvOoj*76W5*l%Hbs3=y ߳˼NqO{k^c/| /Jڕ+e7L??ggv]wΝ;;;ww[no~=}|ݻ4ߖwlϲw]ԧ>4'?I{_yר`C//~ ̸y+^?enjal§,C?C^{B߅}lWve裏dǞ8ۿ[xd:G?2c<Do]W)luU3mvfr^sž85K\(S|7=_>9w퓟d0?5Fy"M|{YRK򞨔D;v}Sϯ\{{3 * O.Wwnnwɓ'9eƱ8؏kޟ#[;1-|{6q/__ooAio_﷏}c$v%9{Os>>򑏤6/$O};~,>??YENsܷo?~?#FuW07Hcᗈ}A@m?c__Wݧ/uHrKOO^џ:zo6b3/1vJ/OFgbXZt`}ы^d}gĜS$/ě/h;4(7]U|eOLB yk_k'|_Ar>^g/-%ē閖رcf$ Yȧ'$=K/ m4|XS_y.{CӅMy*A9e &cu+ K{ؿ精ǯIh}8g;$ z+_?>x{Ii|Kͯa=cZfDj_u_*nF,kG?cj{7tG#?~-SdZ;1庾㡬ASzk~8<8ï4n(A@J7|s _4/](M=~`qir/]K}oz衴I;K5kDϛxgW++x'+?EkK?/[guP?hؙ/$^Z\*cgۛ_},3?/K5{Mi$~^P;?r0=vYZߗJaz vi<~<~Xx{73,yhbTRk{XXDIKfLw; O+0CfUj@\btz++W75 SuTeҰ(jsZ"P9Ր_:]QcݻmjGS|S2?O#F6FXSgK"m=]Q&j~WM}QsǩJFQȺf>Ke@ZtԈ0W,wt>^b>^ NQ[E5ytʤf.^]Q y/>1gy( TO:8lϼcΌ/,+Ť);{w?6rZm@727ſ=>.Bw WE+廘 ]QCw˿K>=\XD,=QqLJ8fqښ=&|Woft++L.ky_s-j8qjwȕ#*ҼC'~boo܅-e䋀c67 mW# '5tTEVsB Ϝ9*c{9eC"yW,xՊW(1Kt$TsTYI#W8)7 >6.~$H|H8é9ʷ^ŞӉoo4QV&A,ڳga? 㯔u\js,CqDڹ{|OJ VAr1;ñww~w6a/n3#D{~~d͹(cYdHE~N{1I,z:C / ]H4o'2=|o];.FP7sRJ9|]Ŵɗa<qc]y_;@"DZC'"X~~2ǯ+\`\ $dHmm9jNHheιs2췼-Z$*PKECDCci.և.*T\hD)6z֋ߥ]֔40F.*[j80q^ߝ`01w}XdR>}ŝo$?winnZ+r6l˭bU0!O;~"< ڼ eabWÙ6ۭyOj8pS> lwnC֤NO؛簝:exb&"sp #5\?ߡb4dE@ ]~o/.7ֿʿKyܵ:joΩąiרUJd](*仗M %@U_:i,rE]9s/Vkzcȗa~) 9I| ^xӛd$d@G78]+tbEOp!G85[4H+>wgǺoaPGBWDc?:`++Fϖ&/8uH/7\ >ѿ>^@2ϓxmo{ƶe^|l+g_i@$ 8)6xzfpEփ7^+kyn; }k+h }2-tiŦ.{fݟ8e3(_KN~'GOpQZ +W?x1w{X{br5yNgw\_ȭWF>~-4È)z߉۵_We\D WDU}t\́v?\ιJSSNڏВ۝A DB@~\ @ Q5GfO[/JF@ I킸 Jt#RJz#r.517<;.4?%ytV}N񑜑$p q^,Kȏ6+ε }$X=dsZ >9RZxBW}W3w+1x ƨ٣m_Te/Y[ ]W>{<'~'8ܟ;bzV{{[}3ReQ6C{sh_Q{3BN=YW|ҿ9/فn3 ٲ-]Z+Ϊ4;`BENs|ߒ<(..7'.Tr .Ka?hy!^'g%^dc޽{}5aw"߱Ym(_rVWR구~wa+jԐ%0@vJΤqK}ZseWf!01'=A{S/4Bݖ@ d4*0:2Cˠ?~{]@9UPGOLyy5?^Jv 8Bcmq]~%G•! Z/>;_ vAzJ SNyuZt>m /VO'\?|W87WqU\5| #U ekcw{E(?~ ә=bTC fK'6&KE,цX2p]*D['{3\kG Bh/ A` x,`0IZƂg ]z=|ywF~.WO i688>H1 L ##M obkh]  #?Kމ\~4kbgj5#TqqWξ{{{c˚̜+lG5#3-l{Mx\ΜveeŞ]n`iWNJhzq )DƏ:?NiquE5ǯ2 Nqg-}\w~ k~g^fri}ެK'2\P3wW4 _k}+#ipm\:_+VWx 9i2.&h8k<VN! @ Z:I:n1a'ۦ+䄀B@!z P h̕ =b\6/nrB@! j5CjB@! )B@! @5jO! ;"@u@! 5"@F\ ! uG@@! F@ֈ=! BH! Bq'B@? B@Z#B@# TG B@Z# Tk՞B@!PwD$B@!PkDjB@! ꎀPB@! jPW{B@! @#B@! @5jO! ;"@u@! 5"@F\ ! uG@@! F@ֈ=! BH! Bq'B@? B@Z#B@# TG B@Z# Tk՞B@!PwD$B@!PkDjB@! ꎀPB@! jPW{B@! @#B@! @5jO! ;"@u@! 5"@F\ ! uG@@! F@ֈ=! BH! Bq'B@? B@Z#B@# TG B@Z# Tk՞B@!PwD$B@!PkDjB@! ꎀPB@! jPW{B@! @#B@! @5jO! ;"@u@! 5"@F\ ! uG@@! F@ֈ=! BH! Bq'B@? B@Z#B@# TG B@Z# Tk՞B@!PwD$B@!PkDjB@! ꎀPB@! jPW{B@! @#B@! @5jO! ;"@u@! 5"@F\ ! uG@@! F@ֈ=! BH! Bq'B@? B@Z#B@# TG B@Z# Tk՞B@!PwZ.BFޕVVT*2+V, ++˶\r/ D[fb%k?in ۚ akAŚ[ZZ##ーrB@46"@{!>@fHl "̲- hKKKV,,ba/. NZVPR@N@sx$OgD&4#aD HZi6$kGA:::iήNkooVH !p}"WB`C (,,,Xe9': Kxjv!iYIrj~@j8wĬ$9BF{pa1zQUALN o*I+ D-Q'QO_Qc} Ke Gm#46 Mx׌TEDfaq̻́́$2ԕOcA÷^&./g #^$ ;{cyΐ%弝$=Ԙ6bCC.G@Z>1~tӧ@ r߭mrm64o}}uv)W/"@WﳑdB`dgvfΦggljj@v&2⬀ S' $8+|V+Nd+)șD&&;ZU"Cٲki{ҐYcR/FZ6cno8E*QC"" @~ڭmx˰m֨>/9! &D!Y&!@C)??iS$< M-(yU WzR>®qӄ4,E"~/ OV"a4[N22)Y.b+"u3"_O!x NWĨִ,@mppFGr;#"''ꋀP}WB`(6 $>S6{qԀ,S)/ݤ؃0_wL1m' G'7WjƔ!?4Fm6|Fm۶ H]jwX! jP͠VCB`gΝ IR ?! A*%~gRHB(Ud&5da:$tP$O4n6մbU-nڱ+2Uq }3.ԍ0 w~6z3`Z"VBR %LcpXF@f-q<%HE!r3. 46"{N۹c`,bbrB@l."@j5Ac6O ow!Eґ3\JOCT,K6{; Itڰܜ~ǧgHFj&9cb\We"IX1ȑ 9=?Gcyh֦`;0V-4|с&=1Tʀ} dve2-'FrB`s\|U48Hs֙363;P!? ~ IR"Rө1֘tIIXl ?ooobxO7?nh@"`^"! A]b?"]'&<4pSSnd 797(gM|.>DwA+ú%B`s\UT1={ގ;f3񡦂$'u c.K4U `TO8dJ ƾ=֍!-3|L;/%^qLp_1@YPkʕՑ$ǸݓXk+Z1ڈpTZuSvnG"Mw'^hWeB'F (!P#h)'S/1c~j+4)[ޏz9{ڑ33V^B>M˔w9@<<p^aX7~| p^6Sn`ĺ Ǻ;[lldvni#s˶u@/6KD~411iNG'Oc%n;iJ'My\]54}n喃N,V e"@ Wm'];8;ꏟ$dlzM#T]JDEPQDņbo{Q *( B f;yondvfܹw{sc;;l?w [<ƛւvÝ$N]8aw6bpsםck6=Ԯe] 4+(*o_TB~X_teGC#`T v;fP3h& P<1}U6oދ6w\ôٻ\Q C#Guߣ&Ngt <%EVЧ0ZL'@ؽh{iBg[|"::{s _Xi`$w :"fpirKAĈ,&(<^1$IRF C1?'>+=$# tTl ܗۿ\J=i5v!mmذahD{Y6g<8@,3B?Ztks}HMV\&Kk̑HqÎ&)Hf|idqR?OHN8O`h}m\X9rGJjꎇٕ.9/h[@b(_i!+P\S! a$x~W`\1 c(P,mvwXk&cbDqs?mWgt@PңGO(KCcr VtdK`&}aQ2g6qC4) 4H#FaN Y|ӇcCDnnfD%;S|=nk֬U PP2mٳ`ޠQu " m'3E@ZAظi@NN0b=m }txZa8 6LX_?=jMZ#0:helYc !O .lCT/4^_>VS3-?< %`yq(UJ&7uoo>f,FdUgX_߲/b.\gOYc>J MSƇJϡs jcwXب\>vH8FK)% 06LAD}N'ق9[2țRCk?M,&%npfj/CS"0("R)S|zv@ҷ`?Д75Y 77=gZsV }\dAemPb|^՘8O0>A#8#'Gt~vrh(;=`Dg͚u A{J2e;?JZDmdj7%/려4l[rG ABYeKH G=Ε% i<pټ/kU6jdM b)QXB$|>^8QxyKv۸4a*]F]YغÖl_gw>Ԟ6&&KhXgqcfLj'6 PwqÝϟ  oc4 X_tSRjxu꺟W)~$s|XB[sEqIZ/68DL]Lo1m=fc^#0mZEg!MdUT|*I$)($a;Y GfŁ@BHCn: Aߴ8q>5BkXF85-\w8d"N8|՜[ޞ8.m(gNE鮘aS밙\8&ćن@ib|UQHF\%]Zͷ,pfU! sg'0~CmѢŮ˶CܷCn?p@:uo}4a c@ Hj?C pa|B޽1! /jLJh/n|H[ +, ?'Vx}.Pڵl<|cHo~h%n_(WʲMKN+x;Gi|z8!FR%St͑dH-8A==`v>Cl`*8>w5ysm3lڵ45սz0?6v(|Dqf*^D@  0 ӧM?ֿ2>d2|~=X Tu {1o~fۈyg6#KS 5Ç?h Ǻo0Wq]~#RX {uyp$0D:P~Nj?FjʋWADHy;PCUWuA0aa6&߽e!5#|Bê* +)^t )@>,nt ='\~fz׻0 On't-Xjkk[,::.\!5hxܬkG?}kSOO}Mgu*T$n^]dڿ.Oȍ.T,܈Q Qup ~_aܨwu?!-qRB=셕Uvlz(L\R:Rzҩqޮ8vifPF' k7cj}gVbXr./!([Î4cW 4[daܹNӟy5b2?F~Vnp[y @_|g+j>)Jͧ~Uث6{e#Gwɔ7t]|쉻8K{w_9 K/Sa`c˅#8zlo͛= ?1" +ʰP񆝭<BS_X%s`,+̍IBSumX ]Gj~0i #?I!Io;yC/ك/?-@ pفt[,3uUg.囻rؕ\cI~5T!=lu~_fΜi׿+^;]t>C_9+KObr?&ڵ^HF#ˍys1].O~KZ"Tl'QIK.R,*DSYr͛ Px ^yayXhOd/hsTcdf\`$)(wGjmŗ}jDWig?jVw鷿;ƷúG;@lBα6Q+)vapi'p;wˈ]B=7?I%V__og5y; |?~6m4|`}CQ/K;Js!حh9c-K}CAd1,eÑGi|{ 矷׽uh(ءڬ gu|6ggCDŽzlСvgx[(K_}_O~6lذkiƍn5zӦMn9q~;c|3=tO[>˻ ^2e㮸 p)U}@XWzjֳ?>K_^"ʮąQIȺxԩюr∼N;~}GY7;uMim鼧cK Gt?ªςN.O|A >2dC&n4Gy`C|;7ofᱡ79[&g&N~W /{|+ve׾5c;}qBqYaml{|H{キ?(TX9Pɢ"ÊJ +$> l5PC>x%ӟ>0]c􋢒Ey/5iҤPtfx.T?އO@cY2{9L7+]$!&g*Is5>ʏyΟggÊ{|6{l7e`ŹyމeN9Wjyٶqtfh\^r'qNm+Xg_|E׿ՙR`:ÓO>[Vo8/U*TV>1WXNKFŋJT/[(n_zǗĖRˈoF~}hEp~7G\9իD2Y> Ե2G]ly7GOM4k\,N6v5M,WGN 9W#FG]|[t?E7շL]?# }RGw3F=DEx>^b<{+Grx42ć(E_ZRP63/+5v/GlQ} =#(7^^ВNqσi*im E!;6 G?x׃ѽmΏ$}y}6dڅM!Т@ A| o|GdxaPCot{ N"6(>she94uxJ'̓Z9؅^#hFY!?,-n*wn,V>!>`Wx>~Vt۞#HF ucI_$VB@_VMN(H]4sJ9vlx/;BK iw5qޫ .@R|q7^fctc$kf Iٕxn`mشٻ!}A^ʍPX;#&mg.r`3tZ*Ciؕ Km%*v9+RJb]Gesǖua)):83=YBM_∬9,3 Nk!OHLJ~<|BYF P[(ee-#芻t(‡:@,GDx-4A+ cM:q !*4~x{ 2f |PNk V.ٵ\#@ Ge%oՔ駟$kVt4ȔJy3l7ۀK 0 Bzz+E4J`e }ƴ7#g&/9N=b;LeSXxMy0cQƒ(*S~.8ƞ}#KI[l#ZK>&^ɹ -ìX$ٝ\>GsJ`NDgP[䱬3Zi?Q"Uim"fy|υna'֗dAkv<+eZEZ?߸҂QlC7dz?]wu>#TsDsь CsM7&HPF|ch`A)OӖ}#1BB+W(JMk>a7**(|ˡ陳39ol8z } T MɷXy-偣7+XpKXDDN͊jw 9`R?;r!6】v#i5Xx?8Ӱ4vp;b6aT_YCS"2u`Eإg':ڎGO+,z((,6ȊXle.==ޞk?jEv_B\ T-*9|&إ̮>O|8_ڮfOîm.Af쪦RBq%ٮhqΥG\|䈫B[ed=H+3]؎e5֋|c=ɺ5>b4?vMJ=Rݭ*hj=>PVXE'qQEP4}@B>bnZ8OfdOȽCF,|X&*t i*w$QyG3P> #`1txc(\(C6`vc!pnbsW%#9bacPxLx0 WqMcP6#( {GatQ`AJk̃>z99ɇCXim;"ttKcCC:?L_֛PAAi2 G2wGZq$S:xJ]<~Ȑ!ѷm߆r)@wcYPz"tӐnWA%8 x! _Ŵ#I\K$qamDE1˕GxXCxFVTamQɑ ˺ql`s>p0w8{R^3 G:vo?  N7)Z=>F[##t8#؟N-#dCkvqTkhjodS|T7vg%\ӊƷki߸sni^L9[Ҋ@bKR)hT m*啎9#ZY?mVFE7ZӁ)m ȓ9R2&nh^2iS4/\AD@D@Ds H\s2gh&N " " "u>%ikNd " " " )@pV)" " " 9"a9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆl8!QD@D@D@! (*ED@D@D GfHlHʆJ)@9ED@D@D RRD@D@D@rD@ PnDȆLaIENDB`gridtext/man/figures/README-unnamed-chunk-4-1.png0000644000176200001440000007771113760022323021033 0ustar liggesusersPNG  IHDR@EiCCPkCGColorSpaceGenericRGB8U]hU>sg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U\ 8eXIfMM*i@B0w@IDATxOϠH @D0"A90 $@$@$@YG(^9HHH   #@uL$@$@$@ @ʺW&  wHHH Pe+ P;@$@$@$u(IHH(   :@Y$@$@$@@ d {|`    ~HHHPֽr>0 $@$@$@YG(^9HHH   #@uL$@$@$@ @ʺW&  wHHH Pe+ P;@$@$@$ueIH -[`xf|WyQvm~ӧիo&$K1Rb+xL$@$@p]~;ƍvahժ7o[⧟~ªU3୷ѣ1l0TP!۲/ $E()\lL$@$P͛7wިQnk) p%X ѼyРAš d}2 @vظq#T.}2WQQFxꩧpGaÆx$e#! &n:t Fe]jժ?̙3ѯ_`'H-@b;  | /OY?gu-Afe& Pe&" ,#+~9眴^-?'OF^(Ҧ #@T^# (@ŏ#A&M 3(D9 d?>c9ꎲ/2B(#9 dtď<8(;#4o᎘I$@$P*~kv8܊nn'r9(P4nO; @vHY#}*UB`Y0'1!]rE/F>a+.[W,$@$@@Gx7U>!=]s-\$ . $%$9 dTO\ɺu-2˯Qt΁}5̽HD 6Jۑ @HE(&K=%戵{ ~ڷ|Qd)FņE*/ @HUO?~5Xt+põ{`0<pƍuP%Bm"@: d!Tŏ2o.87J.p 0Ox<:u 3Pƍ=OPخ D$@$?ua4ǟ0MՁ3NCp-M:%()\lS fega{Ӭ) qdk[ 7[yïJ[FM$@y dB'c03뾖%x#YP5%P d'L@͂2\G !@j26^^p֨B5'OI}R%$@$2-~|\ K/*Vsک~ÓHО"3ME0߮۽֭I}R%+(@$KT-?*~!p]p*Vu)ݧ5W] k4tPhӦMmy1{ p|{>9 @G#pϸzZ58Y\OS.j HԽz‚ (R&Y;r l_> K`G ]s*WW+Mq9cC=dEG݌'YO(@$mvEM.0oyoA&j XRtC̓\(r) e;\>޼Fv;vyf: gVxCG0WJ"(%lYщNY$@$ !x֭Cnps١H[ew8@P*ݏrO}!&g="J1c FT>eRw}ڵkcw..ڲeumE#6lW_}.]3_cYR lXH+^"%Pď?/#-wvNȧ.{G̫ rʗݾ^]wO"(ide2S~Wӯ_?sQGi۶9ڵk3 [n57"ŢW ӨQ##u֭[΄Pބvٻ\+J]Xf_{5Y'|rNA\]I8nᆌ.%2fya:,jcN cVZ,n1 N$.{4c{Wn)<K%S{:IcAZOBd2nʹJ_I,TJM>~O?t5o[kR}swETQȿW Pf{n<'(MG'hK݊.3j\F_ Wv߄3ุ>`+{_F|P'ʊ+_|!;lB!̝;Z{[o5m}YtI %Gxwъp8O?+'#:kYln˗/Dž^f͚ޣOpG`}!Ccy8C!~h޼݅_ǯO};v^{Y*{Ƕ1 @@i?|϶=wӪe~]%(aTeaYYS=ԫWψ00]vQ}7|:϶NDB\Dadwu%5SOYW?_o~;õSV-?Ms1Gyu뮻̽k>ਓ:1D:Ȉ0>rH#;OY&2+f̘1F,ƍm'Fl2"Xy-r=裏luhDuN;1^y啶NYj,{./:[#ȶU7SN5n9EFH @iəq#Ot գ?;LnB0juLu;>=O;o_REo4.rʕ+1jT|aJlk""CbsIpmE,7~%.)ɗ_~i^z%/.{ܣse ]P=֘8:lO}t)I>rujA;]6+̿_n%8NŲEVbcbq%tu./btyQ4imiܸmԨ${ԓŋۺMO8=z$>}%"a&^!˽ZDչBa0Sob+l |=IeJ~=* QP"fcS,{uXPQ}"2zGkT8X_C!֎<Cu&S_آm_?Xa,q ܙL-2+~WJ}_|޲KZ{LN}^UOK=5:4 d#,$:YwZHH[ߘrtW,muL"}?|8Y\hZYj3*UsEw.:u̖/u9ꎼآڴicDwiL `+;嬻Ĵ3ÇcWrfď:٦i=twXzyLeE##I{ N]؄*4:LKh}f#>L.ڿ%C?6&֑9V@xe6Dvb@} ?ZڣKEE>c{,_*,] ӥ;]b9WoF EX,:|+]Fj R"ug!l":Vl*`}w_0o.7l #_BWRͦ<+ʜ*o'  T ,~ 6_!0uQ]dluW{I`ЮmsV#Q!h.JSikUM% ѣGFďY ;<:n IA濾wj|m]7M @Хve*`$Y10ﮄya&sBz:~q!;TOQ%0gI Ksp$@$UtklD^v{<^0_C8yVjG⋟w:t^}+5'Sڸ;,5~%-@%p>$@$ ?~V"͍l0@o& q:2⎽SyI$ ķŏ&k1HiHH,#<ڃf8/ (FZЉDx]gRot{4ađLlq.?TAbk"@⦛>uy%(?*J;IHT5cڵ 0߉ɭgڣ#Yp3k߅ӪetL#S]v_hY]򒔐qe@য়" < ̑d2 є I @<MXɁ%jBbŏ6sd+T`D8 8K+VxOUH`~ZvX^gl YS!Xp%(aT%!P|- M@S3膅P䋞F[TyR%n%W\H\IyT_U q:JSlDT"_ 'E$@E|zsp̙E7.HlY+-t>Mw(`UN8!#sSˏƔ)q?SS$Jg Mi $@޳|Zm1rgͱSL)ogem =3ӆЮ͌'9KK?HNCc7HnCcL#N:H.H֭9ǩ<#sBm)WK$@$C@S#$V;#[G|ˏ 8 H@:"(cO`ch H6zTVhs&KSb z5вe;IO+" +,=z5`%?c_ȽF­nwoβXV6Gx#pa^*:ԟxXXJ; C1u1K,JڹwyeBP`uqg5ͷPjƝ}y*WѸI۵b瞛S1={ry'[3}tSOi]پ pXң dZ%( P;wo!09uzݬ# V?7;i39Tz 5j|E䓁8j 3Qg@;"(4Z }i2/iҗMQѝcSC&w&})gޅHJtDPa=09H0!|Ox'6svys?WLg {*0`@jz_[~Ƭe3π~>x$:R'Z(%v\KD$@@a=c8-[imb$Vn]}{?q)h ֛5c=({JbիW2E3sh  RH@v:uꘕ+W&5{; V0!7;ol1l]ȣBNz؄j50+I5֮乮 ~ UcSx|&7Fd0!2<2$Rww1gaL ~{w>}}VJ}̗>  'Q~2l2.}ٸzoLDEpcxp\]2';nTcF˗7fԜyƘJǜd(%X{:Ae"u BEÒ N]#Ap/?Cw00ӣ[d[pdV#۲V-5=W\vxSqN| ZᲫ_V6l$RF"ƈ@}EO#Uw0:FTJ!G  2I 1%(񫢟fB{~0ѽ䎆^@1qH )13fɎ"^&Q?M;?lt/ytiI7EPc&Se&" 2F ]kCDsq#牧M0})3BQOL6ˏ)h1̈́NO=ERuT}a~f=rDP @.@۶esGh<^ODò @_+8}z=sMw&OJ->-y#d'.IUF5{J$@$tfp*?F<%1Tl 0C,CGl Πam΁.c)_ >b8#zmFT7`zOQ.-_Rh-:tn!SKf̈,y3.9ג9rX2"m)g$@$@#|i`~'vRT8\C/:û~o%ѡ5ӫ'7bOJWNcw@ȊG>.&O@豔 EPr($@$@"W^X`8\-8 [~p?^0^vd_2ٶdX?uw%Q!B8@MْHH'"h1T{7*i-_~%Gvj4jnpp8Zv}RITׯ'/zlT_'SՇEHgX~?1bG,XE|e nWwb  ȟ3k6+W̿AަM&`Xݸ믭r\E>>0aBO&W_9hVzor>hڹl,_|#N&$6sTh̢EV=[YjU❲%-@ٮ$@$AKOpI_~ԫ7"0bʽȦ.ҧ.1N,^1O줚5}}SeUo?E>2@s,HH`;H%ؙVLLqSm3VPݓz_~tl+SNGM2ޚOrZtF0f|/G+c{,z)Z%XHHH Al\kB{ePVŊ&l_i*~*T 1w`.<7+/CR˄OQMt wͷ!EP^,Vi$@$@i]KiwiXxl Y۹;CY.\4ݻwo3&OGSw=4O}~F̸֦a}=s85j4:]:㌜,*yE2thfEôcAn:!^5$@$@$9X FL$@$(D_wƋH?/e+~Ψ.\dsYSFA; j4|xGNnˏ Ԫè|Y8IbS  H@:"(gR.wu2ުvd3 5P we>7 .N+v[oE$~N;͘W^1s"BIBrvDWIHR!P"(Gn{}fju t[|eݺHOǷv[{Ac'{"(o ۓ MDPQ'$?Cl2I qNa{X}7ކ q]?4L)oB5:rY9aG.]reHH`;ȴ*JhJm8@4lHά8|&T݉x0{ƛƓlV _zO[|1?5j#YB2RQQeAHHR!)Tѹ-]TREk]J*Y1tgZ!=Y g?tۻF?bq_^}iTƍbŏ[8z~g˗W] % NAQ>YkYv9@L5$Ouzi:x~kܻD}Ot˼FN?$E戥讻{6 ĿlI$@$PL|&}lEL ? L=̰aogԯ__ Ӡ %'H󁅺t7=[n{u]gWESԧz"b&ьQ'SNsB+TթSǬ^VhR|Q½]2-j) uLzA$:$cQs1y_oϚϾ, o&IHv(tDP%5rYA@/ؓHH#4OEqQb>8ugРAqO%{+|i&|EqT@1T xnyI&ۼT Y%Vt~*D%c9UDTg= %߿7~zϠu|VtY *E 'p[ZSf،*WO2Ы KG.e֭Fw+WPt&|bkь#yɄvmfB0U{)N9:w`SO\%Օ5Dgk  L U$,.)G=k׶Ae,?z]4i5nr- !;"~ل :@ŷ%stdLfFv uYq%0G^SQYA@|ؗHH`HU>cq~ڊDďnaW\qE4P W4{E}}BuPz}xj'fFb&|ES5B/c{,"t>ۘ6P!R'0#!lٴI`)$KYA@1/$@$@%@:"H3ΰ^G}t>?*zi~_7K/M:gWA36:1&"HܧS5xѶ3īJVi ȃ2b |1;RDPʯIH7tDygC|g?#Ig瞏 RhY-*B%V'uY;toF0_F^"[5."( /HHJ~aҤI8#rʤ,p衇B:,\:ޖ-[ 3f̀*pLMӐ\b)w]3NMUhg [yo_9p]`=n>n Rt8p > |Aj^qr'$@$@$P j RˏJi5ND-OӦM͜9s"j_'boOvyoOPZkL*Ĭ&YAmۦء <. U,$@$@$P*A JMjբGciT_x> I\ WRbxgEg 1ڥqo֖C Wͩzȵٳs.<ؘ5Y7kN\GQQz<% (=A~j7S%oIFR߂jJUwBō&L qivy-&Tq{sit$:W ?^$SHR ^/ 4鈠eٲea'tcHm[t7Vy".q6y&Բ]Ҏ]LVZV#/L~%K"0dμ1XWQ-~$eZJ4 ^1; $*GgE:ۚ=V7c,\q>Z^_ԥ~?Z/Z+t{oPR>G1U?vK%(cxclO A  (v/IWi]"Å-oP%`U;M*_x1BjBm;aXQ̘MbZhʘ[o5 )ǿZv}wO?U%[B$@$P HCL<Gy$}ݔhҥW=vQ_unl#bmOA"FǴ(6ha֯[+߇Yξ pETDcVllY2WଳS#3&r?".npUWbWSw d@"H,7HU{\XLD5ʶSxb@IvoTlR I 尓O>٨S 6vfߤɳr_eQGe# ⽿ʸ_My_}e4fwPd瞛;Mi]w9Ĵɏ9 ȭm1u9} w8  (NEYDXZ}rezX~u]p C'|0ӄnB!Ypmj9찜5>Z{>ˏ?.#;iXy @q(L)g+Tc4X-̬YL&Ml}FKQ=b0#ԭ[:OA_QJq/"_㉃7Μe4QoSH/:&cʔH =ןdb'KRv1Ў{ lWכvY%GuwXlFx2Pv),| j4 :b{~qIk˿D6ӦMOK'wɷHH $;("4Tk,Y5j@2cڵbq=o~o Ήv]x]"Hﲋn>^P*hQdlf'KfIcY|ѣD1NHH#t,AoL|eل_n;uұdm=?ys9MJ-Ĕ%j#a5j# u*o.DrCd$@$@$P Z$39i7pڴicg}qWX+&f=NݺN $Ou䫚(Ub#vi?c2vld)̼8 \_"E>n#a{U^=ZdM&PEKƒ>(%| -@%pZ$@$@ۗf.2dHJ"HHkb.?83qaO!aL3 8rFOI~;xG8~ M~:8gaSJZi?@)jv$ (kAɰ/~f0?p=$!mwyA@,0+G~R$N g㚥{Rď>/PoIH!<@ Lb*wjׂo:"eɼkc8kA)߿4}m)bG  (4)SpG@3]׌m[ӹO[W8qW@|#~ s7HX8{5O֥USّHH(.<.{Yϓժe7UƔ8U*ޢ0.>A"*T@ ?j2WNK'J= @ȴ#~d+pl?QmMg8{':fͧ6vJd8ے dLjy8'=4XR=]OΞ,ynA,@ PZ_v& (2a ^ 31:aN;SnFS+Fq'iߧ!K$@$@$EaOs88!NlQ槟Fuf,jyNÆIPV,?3Q$I$@$@HG9φ3l A osXw΄wyY/Ixg$K/$sEA4J$@$} 2$vO/w@ ^} Nl~`0mОDꤹ`)GlF$@$@J K%ذAz=ս0&\+ @Z{}Ap:8 [reQ @9 @F %֮Ӭ;x(̣g'ŏBW @A)m{{+Mn!HÂG$@$@$P,A8fC$JPSX< @H{9l"x'3I-@yHH@: S2P Ohʟ kIHHc 2 O( f+$@$@$PlALpJ\+ @Hw9IQF'rhFlA$@$@B8DOb(1NlE$@$@B V-]4{P$>@bK  (63fq~;vL>H|=z]vx81@@  ؾT9sőG4h}sτesC.e糓 (#F%K]t6l\$L<[رc)~ O=-@@a H6m<8ЫW/4 %oo/㏣M67no9Rwo R8a  l!c֬YXh֭[UVEƍѡC 8M4}N `$@$@$@}J[IHH2J(89 @i @TH$@$@$Q@HHHJ 8G   (NF$@$@$PP9 dPFqr0   @4%ΑHHH (2 @-q$@$@$@%@QHHH4* os$  (2/&MSO=^{-<(<F$@$@$P: 8FJzѳڵ+.]p8@ `?w޹lA$@$@$Pf Y4w\uQq/|2dFWϓIbŊ^zɜgE$@$P +ճ/d_5*W͛7G[GxP2 A=<HH2E ;Ɖ!S~`tS6ܱH:Ʉ%;a GVn:}_U-@-#(zS v"%@>2M'+\ŪU0'pt76!Z,h[wh02fƽ40:{Ͼ" w{>T?:LJM;_SJeiӦ2= l?j{} cśOaú ȖslK2UߪTX@ĪuJdQvKk'~&`E-}ld9Mw ȎW3_n'|zTV-% @JwrIht렽niVNtQU=ӽ @"%!  (S(Ð $B(JlC$@$@$PPɇ!  HP"؆HHHLJuw?L]IHHv 4wCiPtW[ާ/'x;pm^pv3oG$@$@` V"Zv*/IѪK6]Trsq31X#P3ij @)"@$/ٽ!h-NT*T;W,;wDfje8%`1mjU$sM d- v&ITsQN]tA9ɑȒ;MĒ#BHkӪE}%t'$M7s1O{p#q/-zY*pXqu,$@$P\d^lk:~h |y{] PrB` ,9}$!p08[z-du nAHy7fH-]»/)3$w8x'^m6?`^|Ed[ d|]T=D`%0|P$?Y`L|MF?J5_9/>v+˟spu# (.?#h NxpZ><'_~=X .vŚ5kk /ti 1gQ]8m[L_{HQ-WW:mKg$8}Mjumx1[efé]ÞzK3jm NZpN= .ȋ1%slK9P'\s:us5I &8s `X G昻u˻NLөue]" <Nf>x*YHH8ҵ < ^%˧:m(*ר{%ӊUePoVSY~Qם[̺hS@H.>JP\ȼfpDh?z_7xB@<@eDOg$@$_twNB;oVB)͋@ W] p7 ib3\+bAͪpߣ4ijCXxRT&r_GG{ѣkv-_~{Hܿ+GඋowH[ukBo75rf&<,9{Ƶ C ߧۀS!-A>J=\/I~BK߇sP8\(HFDH7by{5cղSZjkOa7xl [ rG ;r8M|h[r5=CE7L~2 @zR?)|Y_ݩ,;_h_:_ZT??7G;̖0,4?acN]4m[uRvϔWo\l*?[{wkr%kE)(G'B*/JɥTԙp8r:.SGQfZ]cf\k޿{z3>d1Dt(;PTΏZJx;KsrܓYOU?l?{2$1BQ)M"KƂU%8qI.|ޑS2k֯ĕ.8%Jq+Sh?'gGj}@7s<"T1c8LѦ{wH$ZěaLQOML$#KJubT\!ׁ!!Ed6EnUƬ.}=$!PE {\f! 4&jŕP- msh{IĤBrgu͢ e":M;{XԔKD"13`L[$Y"씭2qкKUfRCV)U.Jf66^흤f;% +\R%\|f qD4@f#I#,$@$@ 0J~rS %%ңs4UEE]TDS̥"P8 |SkYmyTL4E]wCӴ򥡢g}L{1=P ^ X-~|v)|$9@V93RPw>>fvk|~GV[QESbw.'vEЗԲUk3t %PE;9  vA%4U;.០򑉎O #GA$@1Cbۻ4RD'P#! 'p1ŏ&EDt}RE?9 ZlQw>@SH@|FpW}CS"1S̜4a 9Ʒr0,) @A?w߆cޘ[Q/"oR6oQ'BMON纻/ f߼r A s,ޗ75F/z:=k_kVkݼC0Ha]-j}e~C@Z{n-gs%[kk)#2:!H,&QQiP̝!(Ĕ[P9ưMDPڙ4j k?Cҙs 0$ѪU{r&5ϧr{ ;V+~KW '[B m(/hqy `l. d%Q֯XjWY/Ƨ |]}zx%@t7ڵ!u`hQ|@ɌsXՇv} SzQhCErrE/I jjV.Ep>uar!h^}1u^@։Pz PľK<^6ˁZkZ6E^Oc3:h7HGlW.'8`>Ni>0b! ?qs|WT)V&:' #jJ_UYIIIf Wڭ7C1d`CR7X MUų~+OMT_y&寛7oo ٩pm\h2N{e3_n~hShW_}1 9)*gdhm 7۠ @9?jP'S5:j4c`Q-}΁֬Ay׀niw: zjPBH|?ʦW"}ժкBx jy#?N&o=S98P![yҞf7ŏz5cAUc@;9}ْ񴎭Gun]*5DTAP_do? (t謏+%o^=,יm aMVQ&_H^UY@ ĖzR~!YV:/ jr@R#f-qʣˠ=4U?9CB Y{X!~x5W1O\z[:(sꕳquhDORQT;׿e~^]997\{ύsvt{X7½Kr>gpIDɘ2? |8uMJ"g#a|X$@1H0آI':,D*U:Ytz|0~aȒ/w^p^PC>* 2 ';h<y 5$BB4!|=w"T{4ve)vAWIx7lm;s B]_W!F/<Wˆ{xkd/$@G.Hmw_V-Œ1851gIJܦ]7oS?v>u$R G%AYE(k%_EGzH~ڑ%*8uƹtQKٍ\03$-{?X }dw5#,ƿI;pD] ʎƨMEidO }bI.=TBWIVYGE'!S[zk)!1 y|e+t(.oRaPSh?'ъ워WԶONuh"LAr{][ה$@1HNӰc1q@?3`͂9xjA~ a'RN%a4hh^0ݥrغq1*W},B')wˠըj.6f-w % Y*Lg;UsUyo#SLRtRjŕw$msh%*y2^5UBrgu͙8Vd"2T; ,.4.=bd} IV,T/[;Ze&E>IoeR%>dfcj/iϙ썏C٣v^Yaf\X:. Rl 5MAn7`q#,$@QAbsX4%lll^G5|UTg՛aݗQ#4Am!@d JKGi<( KEPdq@Vֲ4tSSm*T]v Nʗ=}L{1=P ^@djR>Ն^i"`"UZK~uԳpϾ]e葕f%~Tє*ᾋIMJs&$J1Y>yWmATDBc1Kli|= +ŏb6s \ jIm`#%7Ht] @Z(bjUOv"(Nд}'x@)~,c@q;B$@IH]^ ˎJ h~)~R?t@ǒ̯l(W3yP{r1xH:v_ϥ"t J~NOI=緍 ܰ]{!Yr +o 8Gf~? O8"mL۸q#Zh!Uq]Վ"ݑ4٠dr :tI"E27s$?:Kt]7|ds O"YL|b 7GC <|Uݱ3U*WB|t#@G18wEYJn<.+OBB$@&`9{,d{ ŏph:frȞ]" "`Q%AF)~WP*&V" ȏ@8Ds:+?$ĕ&:>2e(Β #~˲ ,5 ^GD Q(~6)"O% 8@|:Tg=fm[0ўmf6dO䢣Ï \tI0u(WQb%@(-XiH"Gh~w><өo)~~~Ln9 F~,GHH?7=j*[Zu1ae(SؒOK0ƔFb, N.ަԉ%J]3q.;]x3 IHnr~ԴM|9Aҧ `춧]s$@$`ŏZ9} 7}?KqgL񣦽ʖS9AƎ̷]aRE"$@$вu4=Xn^f>?^_c/mq/:H̝=(O!AZ  @8p*UQ4ywТCW ;(~Te|E @t $@$$ j֬zn+t: t|ysZC>OX8H IH hFzu1]8԰k| Tsѯp^@pm( ې @'t;l?a#'Cx! 8@ uE߅X Py% ߍ3',wI/ֶIk$@$KOKC%C+O"@ PK*9I[$@$B f?b]; PIIH  %(~"!LHH TEP{p O8*m @P9A}o%L% Q6 F̡ XI@Eޞ; o=QX ڲ4iH@"' hH@QLH"kYEvHH F(1ʼGPR^36V>_mXիx$9 @P/oAǁ[9^_] ܱ'r=e(f]ρ @xбc'=Ca4d7%8&#^5 s ljHH̘1y=")sΕ{$rO# ~-222Uݻ7=^ P9+ Dv̙3TR{ 'rW'  .efɨR%ʰccpIHJirFÑ^zm۶V-mhiHHH8Tϰ_$@$@$@ - 8S=~ F64L$@$@$T@N E$@$@$` 0 S P93 m(lCK$@$@$@N%@Tϰ_$@$@$@ - 8S=~ F64L$@$@$T@N E$@$@$` 0 S P93 m(lCK$@$@$@N%@Tϰ_$@$@$@ - 8S=~ F64L$@$@$T@N E$@$@$` 0 S P93 m(lCK$@$@$@N%@Tϰ_$@$@$@ - 8S=~ F64L$@$@$T@N E$@$@$` 0 S P93 m(lCK$@$@$@N%@Tϰ_$@$@$@ - 8[I&IENDB`gridtext/man/figures/README-unnamed-chunk-5-1.png0000644000176200001440000004201213760022323021016 0ustar liggesusersPNG  IHDRz4iCCPkCGColorSpaceGenericRGB8U]hU>sg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U\ 8eXIfMM*i_?hIDATx+U?\TT"MQA P "ER& XETx"sMv%óϽI&Syɔj @@EW!@ P(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @(@ @*PVma @ P @Jr[ @k @RhF  @T@Z) @C@ sOxøq +]vY8cD@~c$~)~``i# O\ଳ s]z[o[o?6/?=xo7K. {q@~c4lol ^m=$|ԑG wLXyt gWX> s1Gs!pک]w\f(5q$@~I2FoTI(Q%n8򈉵E?nNkn-wy罉PLP`"^ cX`+^ p\[(-Lœso _W\y]x}χ7޸<1-<.L~ҌU. GuUsqod,gn oGr IݑFx$ moNԞטݕ/vxSwuamW]}MXq.ȯ 8 "~-. ,7,Z:Xz$V Gp]wvYbWfnHߐ:]k|oK)m|_l1=xlRHmS/r7dan~.D~튵7JmlH(@d1dWi. 'W].3[KO~Cjo[~Q| nK9ImSN^U+_rܸ lG#Kv<|8: j#7!/aD5`t9J(c aΞaݑ_2>7SɯI'C׉O+Ϡ(@KtvWvXg}!|䣻{?q= _aӏS[|^nλ lAx@~d;=ȯJ^/0Rd8+pX)?Vv&w߽vw˭ cc]"za [\ClZ {`; {! ʯ wQFE+@;ɇ 瀕 8Fc~wϽ65<ק-e?R?E~:C?9;c~^s+i'n,P~P4M7Sf'd1Vrʦ>~ wix  >Dv+g3/ ~K[yLx/pؑ;#RL.R;ɯ%˯ʯX"V%C]u$_~z-©?A[CN}KgYg)}B z/~N^+ =wz*>G6v{+}(ZNN~-g@ 6h ?Mf;==XueTSMjZeo _#laOnfmϧ ᭷&H_ʿN>7,Җ+/pxTXɯ]/])J%aJ~=iglݗ{)NE 30][B_<d;'ݣ<`_sJ!ԝy",V [ M7mw6kFU ' MZ}u'r.//!\~kO _5n>GM_p7 [dCRr[ ̆0(kk:A1XYk]ºk.K?3t˽S[oXVTae -0o8~r?Y?\aytJ ҅v򋋛u«f|=UV\umƛ_';0S}OElR;}b©g\80n)paf~|0/|[?<`7 OO_3d kl>i@nw''? 7œs7tPn:}Xy ֳ-ݥS=wzQm[a%73JI[~M?j~ /4_}nުiKxV+%s;4t˱JomM,|]a}6fB8F|(,e4Lc~M?h'[?>L?=]믿:0ݴS7]b[o] yay_~=]N~qKAzѥWr\4>[Bc՗ .2'i7N*N_G)Y+ ,0wx0SL^ކf)λ[b_038yߛoXfg\sӲx[O_us8W۵;>5:N~qiq3sZ{?'t]7Vuaz1'F_<`qMYK__X{$YwN uyvVSV6r―,_x c8`%SYJܶsϽX]w֋/Ew~/P5qt-eN'S ;nEhc#ר_W[{p,ʵnZOPu׭' ݭ9O|_lτBW#N)ϸGDqtM~p7 k\q$X=kcWŹ??E(&_\رK>vJ+.vu,xʬx6x`R,B/8O~M?(x%sϿXFzun*#k@5X{O˯-Y://M3_ע'oWa7 of ?ϴ_\GVT~E;gcnxfq@Ҧ[6rϰZ+/m87& 5DY<Ŗxyƿ[7S2]0NȯIwݍwvr:-Bg8k7_} <'X~ U}@7/瀕Q iŶ_9uz۝\2}}84~yon{\s^jƧ/M?j~qE?QqErC89)<Е}hYmJ+,? >4҇_F_ _ N%?{_ן%s(-ԟ=@:-`7S+/9>LqKL)~1UszYzt_  c=kbjÁ.m>ⳳxze.6am7 OͧY I[# 58Ku|g+v[BV<<↗ƿx`;l&}ZAoP~ yZCԟ]uIsOZk/\c7L{YGԶtgX9︜\ ߛ^;5SvQjm^֦7vѹɬ.ʯ_~۵V߳6,3zr3\?EjQ~nm6կ(V{'Fo'wجVQv׶Ƶ)&3ɧo^/d8o-⵵\?LKLasn6w?"|k.Λ.VMs]~kW\tB㣿gu7؁_n(rXlw\2* czQD^y񉵘Y,hZʯZ}x0~Q_ L4_^H0>Κ+q7z|PcUd֫ Xwn^b^qʪ+/]/D3'OnAm?ܴ>> ! Њigxȗk -0O;ΫkjO7M~^t)P7_{iu7ءs[bZ?85o$n^d<rKZYi 7f6Z}EhZ-V-5/]M9ot_[;gqW'Иav71Cjc9wϽ}kBOnen~#57H6̷ƫO)~NY^}⧈"4~0^{Ok{axV.m'[M2[Hc^w7ͳgN~q?:vǯɤ0]&bzpQ^b~yu78N׿2G?o[f{.;nޓk9HQ:m|[f~Mלִr*BWΛgAY]'z[? c!Ӹ/Z9]wcjq=?7>"T~_zEgvڢP4knvqn5fX쓷4}NZHQ:| vi?*иuYe:ܳ}M| M~1EY-0/cҁ)\~w hc.9b7/>qsm],r{gej _wskg7Y/'y6W{w]-A_w3l'eN6b ֎8+=Y˯U17)5X~Yڷ38ϷYPy|vOJi=nO?Ehܟ7QoHsm.ޗ_5W֏>\sZ;zoGz73<w_wsnHq?L!r-_uu5f>\[9{~UQ:y;S-y!Xm]=7,2k܏>8N9myxnvs[gz/ݍҹ)@W5-"eݵViں2"ԛgw/?"xk2OͳW(~ދezIk>>'jֱcY}Xğ1x_"=|mO^”ɯGu7m4gZ܂!Yk\"4;2|,o|] >EIr6 9~y .>AF!q8na&~}bsuWkZl]ӳs,;xnVWf_\mTS֍_"J,༵o~uXZ0aC7|t)nUW_~}ac}2wBT_w>!,ᴟw7s9kxG^+;lIoi q./֨^m7'u6l}É~iAMǝxNp=ڜ"\yIa 3gxV ϿbgiZ5qtzxo_䥰ۄu^%lӗÅ_nbGxiRαaM "ޖ|_~O=tm|1|b󏆗j%^xUwZ_W˯EsM7T`gKi3^aoW|;o+-gq@s`7oM/^4xV[81x{QZhs&8ɯ11UVZz7m [hbqx:hnvqn}1O/uZ^ 1sm6*~n',/gu!Rϰ9_w3/һcyn~Bzkf-{E+w7{cm/\N"^o-'}%STL<XY.e^v3Q%_tXK8@<*.%8x_~.>>yăf|EVtfEh0I~-pPO_[tbͳEh|>cDnY<; [bqƟ<ʽwF}|9 _[8^8,B4n]kmYƫW{1yu&/a/ymZX?ȦP_1jmGUvx饗v345_oNq>sn)nU?b[Ciyv_,B)"t̪խç[-B㾜7_{zz'/Z_]gݍEh̹w0&/ {e]wu?)P<?5ִxڳ,4?;g-64oa@L>P~e\Wg㕎q׊KR{/S@_?_s ~dgSqxׅp0+Z 0JoivS\vVžs_,ʯ#|4[>޷WZ+s!t0@ֿ`NXpסw9<;O]0P~\\\-k#ķTwÏ's@6sCŹuGE}YxGGvq>ʯQpd_\Y\v6H~uu?]jf .Dw~ l"tQv{a \tmx+l7ylԨ`SʯR)0}6Σ/R{7KlV[ep/agD7"c~ .FovܡO?WlW8vکW:z5q!!^ϛgF/d(Eͬqi1C_hV21O\B-', WI cj!.Ÿbp9:o{_o{ۻ.8`ny_l*+/]B_ O<>_J ?04S#>eגZ*b,joͳ ,sd_i_(,7x3L5U~?P~aMֵnWjw/Zb7{tgi]R<bw &o~Du]~}lil5Ҹߘ_d-_)έb8-ip(@G0ڪ}O_d}}"Œ3ЧmC%ŰoXl爫:q}0n:[ogioV8WeW:tӯ=2܉=E_zp-4N~=މo^i+%TɯXo$5okdIM;mm֬|#tT@~ ֿϠȯѝt?WX_/O={n*\Eu* NGwzK_;F?ť+@SLE @ 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu 4T 4pu ÓIENDB`gridtext/man/richtext_grob.Rd0000644000176200001440000001034513760102467016022 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/richtext-grob.R \name{richtext_grob} \alias{richtext_grob} \title{Draw formatted text labels} \usage{ richtext_grob( text, x = unit(0.5, "npc"), y = unit(0.5, "npc"), hjust = 0.5, vjust = 0.5, halign = hjust, valign = vjust, rot = 0, default.units = "npc", margin = unit(c(0, 0, 0, 0), "pt"), padding = unit(c(0, 0, 0, 0), "pt"), r = unit(0, "pt"), align_widths = FALSE, align_heights = FALSE, name = NULL, gp = gpar(), box_gp = gpar(col = NA), vp = NULL, use_markdown = TRUE, debug = FALSE ) } \arguments{ \item{text}{Character vector containing Markdown/HTML strings to draw.} \item{x, y}{Unit objects specifying the location of the reference point.} \item{hjust, vjust}{Numerical values specifying the justification of the text boxes relative to \code{x} and \code{y}. These justification parameters are specified in the internal reference frame of the text boxes, so that, for example, \code{hjust} adjusts the vertical justification when the text is rotated 90 degrees to the left or right.} \item{halign, valign}{Numerical values specifying the text justification inside the text boxes. If not specified, these default to \code{hjust} and \code{vjust}.} \item{rot}{Angle of rotation for text, in degrees.} \item{default.units}{Units of \code{x} and \code{y} if these are provided only as numerical values.} \item{margin, padding}{Unit vectors of four elements each indicating the margin and padding around each text label in the order top, right, bottom, left. Margins are drawn outside the enclosing box (if any), and padding is drawn inside. To avoid rendering artifacts, it is best to specify these values in absolute units (such as points, mm, or inch) rather than in relative units (such as npc).} \item{r}{The radius of the rounded corners. To avoid rendering artifacts, it is best to specify this in absolute units (such as points, mm, or inch) rather than in relative units (such as npc).} \item{align_widths, align_heights}{Should the widths and heights of all the text boxes be aligned? Default is no.} \item{name}{Name of the grob.} \item{gp}{Other graphical parameters for drawing.} \item{box_gp}{Graphical parameters for the enclosing box around each text label.} \item{vp}{Viewport.} \item{use_markdown}{Should the \code{text} input be treated as markdown? Default is yes.} \item{debug}{Should debugging info be drawn? Default is no.} } \value{ A grid \code{\link{grob}} that represents the formatted text. } \description{ This grob acts mostly as a drop-in replacement for \code{\link[grid:grid.text]{grid::textGrob()}} but provides more sophisticated formatting. The grob can handle basic markdown and HTML formatting directives, and it can also draw boxes around each piece of text. Note that this grob \strong{does not} draw \link{plotmath} expressions. } \examples{ library(grid) text <- c( "Some text **in bold.**", "Linebreaks
Linebreaks
Linebreaks", "*x*2 + 5*x* + *C**i*", "Some blue text **in bold.**
And *italics text.*
And some large text." ) x <- c(.2, .1, .7, .9) y <- c(.8, .4, .1, .5) rot <- c(0, 0, 45, -45) gp = gpar(col = c("black", "red"), fontfamily = c("Palatino", "Courier", "Times", "Helvetica")) box_gp = gpar(col = "black", fill = c("cornsilk", NA, "lightblue1", NA), lty = c(0, 1, 1, 1)) hjust <- c(0.5, 0, 0, 1) vjust <- c(0.5, 1, 0, 0.5) g <- richtext_grob( text, x, y, hjust = hjust, vjust = vjust, rot = rot, padding = unit(c(6, 6, 4, 6), "pt"), r = unit(c(0, 2, 4, 8), "pt"), gp = gp, box_gp = box_gp ) grid.newpage() grid.draw(g) grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) # multiple text labels with aligned boxes text <- c("January", "February", "March", "April", "May") x <- (1:5)/6 + 1/24 y <- rep(0.8, 5) g <- richtext_grob( text, x, y, halign = 0, hjust = 1, rot = 45, padding = unit(c(3, 6, 1, 3), "pt"), r = unit(4, "pt"), align_widths = TRUE, box_gp = gpar(col = "black", fill = "cornsilk") ) grid.newpage() grid.draw(g) grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) } \seealso{ \code{\link[=textbox_grob]{textbox_grob()}} } gridtext/DESCRIPTION0000644000176200001440000000223213764504772013630 0ustar liggesusersPackage: gridtext Type: Package Title: Improved Text Rendering Support for 'Grid' Graphics Version: 0.1.4 Authors@R: person( given = "Claus O.", family = "Wilke", role = c("aut", "cre"), email = "wilke@austin.utexas.edu", comment = c(ORCID = "0000-0002-7470-9261") ) Description: Provides support for rendering of formatted text using 'grid' graphics. Text can be formatted via a minimal subset of 'Markdown', 'HTML', and inline 'CSS' directives, and it can be rendered both with and without word wrap. URL: https://wilkelab.org/gridtext/ BugReports: https://github.com/wilkelab/gridtext/issues License: MIT + file LICENSE Depends: R (>= 3.5) Imports: grid, grDevices, markdown, rlang, Rcpp, RCurl, png, jpeg, stringr, xml2 Suggests: covr, knitr, rmarkdown, testthat, vdiffr LinkingTo: Rcpp Encoding: UTF-8 LazyData: true RoxygenNote: 7.1.1 SystemRequirements: C++11 NeedsCompilation: yes Packaged: 2020-12-10 20:29:04 UTC; clauswilke Author: Claus O. Wilke [aut, cre] () Maintainer: Claus O. Wilke Repository: CRAN Date/Publication: 2020-12-10 20:50:02 UTC gridtext/tests/0000755000176200001440000000000013506453627013261 5ustar liggesusersgridtext/tests/testthat/0000755000176200001440000000000013764504772015125 5ustar liggesusersgridtext/tests/testthat/test-null-box.R0000644000176200001440000000222713757770522017770 0ustar liggesuserstest_that("basic features", { # null box with no extent nb <- bl_make_null_box() expect_identical(bl_box_width(nb), 0) expect_identical(bl_box_height(nb), 0) expect_identical(bl_box_ascent(nb), 0) expect_identical(bl_box_descent(nb), 0) expect_identical(bl_box_voff(nb), 0) g <- bl_render(nb, 100, 200) expect_identical(length(g), 0L) # null box with defined extent nb <- bl_make_null_box(100, 200) expect_identical(bl_box_width(nb), 100) expect_identical(bl_box_height(nb), 200) expect_identical(bl_box_ascent(nb), 200) expect_identical(bl_box_descent(nb), 0) expect_identical(bl_box_voff(nb), 0) g <- bl_render(nb, 100, 200) expect_identical(length(g), 0L) # null box transmits its extent to enclosing rect box rb <- bl_make_rect_box( nb, 0, 0, margin = rep(0, 4), padding = rep(0, 4), gp = gpar(), width_policy = "native", height_policy = "native" ) bl_calc_layout(rb, 0, 0) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100, "pt")) expect_identical(outer$y, unit(200, "pt")) expect_identical(outer$width, unit(100, "pt")) expect_identical(outer$height, unit(200, "pt")) }) gridtext/tests/testthat/test-textbox-grob.R0000644000176200001440000002363513764204623020652 0ustar liggesuserscontext("textbox-grob") test_that("misc. tests", { # empty strings work expect_silent(textbox_grob("")) expect_silent(textbox_grob(" ")) # NAs work expect_silent(textbox_grob(NA)) }) test_that("visual tests", { draw_box <- function() { function() { g <- textbox_grob( "**The quick brown fox jumps over the lazy dog.**

The quick brown fox jumps over the lazy dog. The **quick brown fox** jumps over the lazy dog. The quick brown fox jumps over the lazy dog.", y = unit(0.9, "npc"), vjust = 1, gp = gpar(fontsize = 15), box_gp = gpar(col = "black", fill = "lightcyan1"), r = unit(5, "pt"), padding = unit(c(10, 10, 10, 10), "pt"), margin = unit(c(0, 10, 0, 10), "pt") ) grid.draw(g) grid.text(" Box spanning entire viewport, with margins", 0, 1, hjust = 0, vjust = 1.2) grid.points(0.5, 0.9, default.units = "npc", pch = 19, size = unit(5, "pt")) invisible() } } expect_doppelganger("Box spanning entire viewport, with margins", draw_box()) draw_align_upright <- function() { function() { g1 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, valign = 1, halign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g2 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 1, valign = 0.5, halign = 0.5, width = unit(1.5, "inch"), height = unit(1.5, "inch"), box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g3 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 0, valign = 1, halign = 1, width = unit(1.5, "inch"), height = unit(1.5, "inch"), box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g4 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 0, valign = 0, halign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) grid.draw(g1) grid.draw(g2) grid.draw(g3) grid.draw(g4) invisible() } } expect_doppelganger("Multiple boxes, internal alignment", draw_align_upright()) draw_align_left_rotated <- function() { function() { g1 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, valign = 1, halign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "left-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g2 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 1, valign = 0.5, halign = 0.5, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "left-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g3 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 0, valign = 1, halign = 1, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "left-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g4 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 0, valign = 0, halign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "left-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) grid.draw(g1) grid.draw(g2) grid.draw(g3) grid.draw(g4) invisible() } } expect_doppelganger("Multiple boxes left rotated, internal alignment", draw_align_left_rotated()) draw_align_right_rotated <- function() { function() { g1 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, valign = 1, halign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "right-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g2 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 1, valign = 0.5, halign = 0.5, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "right-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g3 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 0, valign = 1, halign = 1, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "right-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g4 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 0, valign = 0, halign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "right-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) grid.draw(g1) grid.draw(g2) grid.draw(g3) grid.draw(g4) invisible() } } expect_doppelganger("Multiple boxes right rotated, internal alignment", draw_align_right_rotated()) draw_align_inverted <- function() { function() { g1 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, valign = 1, halign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "inverted", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g2 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 1, valign = 0.5, halign = 0.5, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "inverted", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g3 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 0, valign = 1, halign = 1, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "inverted", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) g4 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 1, vjust = 0, valign = 0, halign = 0, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "inverted", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 5, 5, 5), "pt") ) grid.draw(g1) grid.draw(g2) grid.draw(g3) grid.draw(g4) invisible() } } expect_doppelganger("Multiple boxes inverted, internal alignment", draw_align_inverted()) draw_rotated_fixedpoint <- function() { function() { g1 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, x = 0.4, y = 0.6, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "upright", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 0, 5, 0), "pt") ) g2 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, x = 0.4, y = 0.6, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "left-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 0, 5, 0), "pt") ) g3 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, x = 0.4, y = 0.6, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "right-rotated", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 0, 5, 0), "pt") ) g4 <- textbox_grob( "The quick brown fox jumps over the lazy dog.", hjust = 0, vjust = 1, x = 0.4, y = 0.6, width = unit(1.5, "inch"), height = unit(1.5, "inch"), orientation = "inverted", box_gp = gpar(col = "black", fill = "cornsilk"), padding = unit(c(2, 2, 2, 2), "pt"), margin = unit(c(5, 0, 5, 0), "pt") ) grid.draw(g1) grid.draw(g2) grid.draw(g3) grid.draw(g4) grid.points(0.4, 0.6, default.units = "npc", pch = 19, size = unit(5, "pt")) invisible() } } expect_doppelganger("Rotation around fixed point", draw_rotated_fixedpoint()) }) gridtext/tests/testthat/helper-vdiffr.R0000644000176200001440000000025213764204516017775 0ustar liggesusersexpect_doppelganger <- function(title, fig, path = NULL, ...) { testthat::skip_if_not_installed("vdiffr") vdiffr::expect_doppelganger(title, fig, path = path, ...) } gridtext/tests/testthat/test-rect-box.R0000644000176200001440000001531513757770536017762 0ustar liggesuserstest_that("alignment of content", { nb <- bl_make_null_box() cb <- bl_make_rect_box(nb, 20, 10, c(0, 0, 0, 0), c(0, 0, 0, 0), gp = gpar()) rb <- bl_make_rect_box(cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar()) bl_calc_layout(rb, 0, 0) g <- bl_render(rb, 100, 200) # placement of outer box depends on margins outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(400 - 2 - 8, "pt")) expect_identical(outer$height, unit(600 - 1 - 4, "pt")) # placement of inner box depends on margins, padding, justification, and inner size inner <- g[[2]] expect_identical(inner$x, unit(100 + 8 + 128, "pt")) expect_identical(inner$y, unit(200 + 600 - 1 - 16 - 10, "pt")) rb <- bl_make_rect_box(cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), content_hjust = 1, content_vjust = 0) bl_calc_layout(rb, 0, 0) g <- bl_render(rb, 100, 200) inner <- g[[2]] expect_identical(inner$x, unit(100 + 400 - 2 - 32 - 20, "pt")) expect_identical(inner$y, unit(200 + 4 + 64, "pt")) }) test_that("size policies", { nb <- bl_make_null_box() cb <- bl_make_rect_box(nb, 20, 10, c(0, 0, 0, 0), c(0, 0, 0, 0), gp = gpar()) rb <- bl_make_rect_box( cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "expand", height_policy = "relative" ) bl_calc_layout(rb, 100, 50) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(100 - 2 - 8, "pt")) expect_identical(outer$height, unit(300 - 1 - 4, "pt")) inner <- g[[2]] expect_identical(inner$x, unit(100 + 8 + 128, "pt")) expect_identical(inner$y, unit(200 + 300 - 1 - 16 - 10, "pt")) rb <- bl_make_rect_box( cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "relative", height_policy = "expand" ) bl_calc_layout(rb, 50, 300) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(200 - 2 - 8, "pt")) expect_identical(outer$height, unit(300 - 1 - 4, "pt")) inner <- g[[2]] expect_identical(inner$x, unit(100 + 8 + 128, "pt")) expect_identical(inner$y, unit(200 + 300 - 1 - 16 - 10, "pt")) rb <- bl_make_rect_box( cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "native", height_policy = "native" ) bl_calc_layout(rb, 50, 300) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) # native width/height now depends on padding, not on margin expect_identical(outer$width, unit(20 + 32 + 128, "pt")) expect_identical(outer$height, unit(10 + 16 + 64, "pt")) inner <- g[[2]] expect_identical(inner$x, unit(100 + 8 + 128, "pt")) expect_identical(inner$y, unit(200 + 4 + 64, "pt")) rb <- bl_make_rect_box( cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "native", height_policy = "relative" ) bl_calc_layout(rb, 50, 50) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(20 + 32 + 128, "pt")) expect_identical(outer$height, unit(300 - 1 - 4, "pt")) inner <- g[[2]] expect_identical(inner$x, unit(100 + 8 + 128, "pt")) expect_identical(inner$y, unit(200 + 300 - 1 - 16 - 10, "pt")) rb <- bl_make_rect_box( cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "native", height_policy = "expand" ) bl_calc_layout(rb, 50, 300) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(20 + 32 + 128, "pt")) expect_identical(outer$height, unit(300 - 1 - 4, "pt")) inner <- g[[2]] expect_identical(inner$x, unit(100 + 8 + 128, "pt")) expect_identical(inner$y, unit(200 + 300 - 1 - 16 - 10, "pt")) rb <- bl_make_rect_box( cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "native", height_policy = "fixed" ) bl_calc_layout(rb, 50, 300) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(20 + 32 + 128, "pt")) expect_identical(outer$height, unit(600 - 1 - 4, "pt")) inner <- g[[2]] expect_identical(inner$x, unit(100 + 8 + 128, "pt")) expect_identical(inner$y, unit(200 + 600 - 1 - 16 - 10, "pt")) rb <- bl_make_rect_box( cb, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "fixed", height_policy = "native" ) bl_calc_layout(rb, 50, 300) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(400 - 2 - 8, "pt")) expect_identical(outer$height, unit(10 + 16 + 64, "pt")) inner <- g[[2]] expect_identical(inner$x, unit(100 + 8 + 128, "pt")) expect_identical(inner$y, unit(200 + 4 + 64, "pt")) # native size policies with no content rb <- bl_make_rect_box( NULL, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "native", height_policy = "native" ) bl_calc_layout(rb, 50, 300) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) # native width/height now depends only on padding, since content size is 0 expect_identical(outer$width, unit(32 + 128, "pt")) expect_identical(outer$height, unit(16 + 64, "pt")) rb <- bl_make_rect_box( NULL, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "native", height_policy = "fixed" ) bl_calc_layout(rb, 50, 300) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(32 + 128, "pt")) expect_identical(outer$height, unit(600 - 1 - 4, "pt")) rb <- bl_make_rect_box( NULL, 400, 600, c(1, 2, 4, 8), c(16, 32, 64, 128), gp = gpar(), width_policy = "fixed", height_policy = "native" ) bl_calc_layout(rb, 50, 300) g <- bl_render(rb, 100, 200) outer <- g[[1]] expect_identical(outer$x, unit(100 + 8, "pt")) expect_identical(outer$y, unit(200 + 4, "pt")) expect_identical(outer$width, unit(400 - 2 - 8, "pt")) expect_identical(outer$height, unit(16 + 64, "pt")) }) gridtext/tests/testthat/test-richtext-grob.R0000644000176200001440000001612213764204557021006 0ustar liggesuserscontext("richtext-grob") test_that("grobheight and grobwidth work", { # width is the same for textGrob and richtext_grob g <- textGrob("test") g2 <- richtext_grob("test") w <- convertWidth(grobWidth(g), "pt", valueOnly = TRUE) w2 <- convertWidth(grobWidth(g2), "pt", valueOnly = TRUE) expect_equal(w, w2) # height is slightly larger for richtext_grob, b/c descent is considered h <- convertHeight(grobHeight(g), "pt", valueOnly = TRUE) h2 <- convertHeight(grobHeight(g2), "pt", valueOnly = TRUE) expect_lt(h, h2) # width and height are flipped after rotating 90 degrees g <- textGrob("test", rot = 90) g2 <- richtext_grob("test", rot = 90) w <- convertWidth(grobWidth(g), "pt", valueOnly = TRUE) w2 <- convertWidth(grobWidth(g2), "pt", valueOnly = TRUE) expect_lt(w, w2) # height is slightly larger for richtext_grob, b/c descent is considered h <- convertHeight(grobHeight(g), "pt", valueOnly = TRUE) h2 <- convertHeight(grobHeight(g2), "pt", valueOnly = TRUE) expect_equal(h, h2) # position of multiple labels is taken into account g <- textGrob("test", x = unit(0, "pt"), y = unit(80, "pt")) g2 <- textGrob(c("test", "test"), x = unit(c(0, 50), "pt"), y = unit(c(80, 40), "pt")) w <- convertWidth(grobWidth(g), "pt", valueOnly = TRUE) w2 <- convertWidth(grobWidth(g2), "pt", valueOnly = TRUE) expect_equal(w + 50, w2) h <- convertHeight(grobHeight(g), "pt", valueOnly = TRUE) h2 <- convertHeight(grobHeight(g2), "pt", valueOnly = TRUE) expect_equal(h + 40, h2) # multiple labels, w rotation g <- textGrob("test", x = unit(0, "pt"), y = unit(80, "pt"), rot = 45) g2 <- textGrob(c("test", "test"), x = unit(c(0, 50), "pt"), y = unit(c(80, 40), "pt"), rot = 45) w <- convertWidth(grobWidth(g), "pt", valueOnly = TRUE) w2 <- convertWidth(grobWidth(g2), "pt", valueOnly = TRUE) expect_equal(w + 50, w2) h <- convertHeight(grobHeight(g), "pt", valueOnly = TRUE) h2 <- convertHeight(grobHeight(g2), "pt", valueOnly = TRUE) expect_equal(h + 40, h2) # grob height and width are identical with and without debug info text <- c( "Some text **in bold.**
(centered)", "Linebreaks
Linebreaks
Linebreaks", "*x*2 + 5*x* + *C*i
*a* = 5" ) x <- c(.4, .3, .8) y <- c(.8, .5, .3) rot <- c(0, -45, 45) halign <- c(0.5, 0, 1) valign <- c(0.5, 1, 0) g1 <- richtext_grob( text, x, y, halign = halign, valign = valign, rot = rot, padding = unit(c(6, 6, 4, 6), "pt"), r = unit(c(0, 4, 8), "pt"), debug = FALSE ) g2 <- richtext_grob( text, x, y, halign = halign, valign = valign, rot = rot, padding = unit(c(6, 6, 4, 6), "pt"), r = unit(c(0, 4, 8), "pt"), debug = TRUE ) w1 <- convertWidth(grobWidth(g1), "pt", valueOnly = TRUE) w2 <- convertWidth(grobWidth(g2), "pt", valueOnly = TRUE) expect_equal(w1, w2) h1 <- convertHeight(grobHeight(g1), "pt", valueOnly = TRUE) h2 <- convertHeight(grobHeight(g2), "pt", valueOnly = TRUE) expect_equal(h1, h2) }) test_that("misc. tests", { # empty strings work expect_silent(richtext_grob("")) expect_silent(richtext_grob(" ")) # NAs work expect_silent(richtext_grob(c(" ", "abc", NA))) }) test_that("visual tests", { draw_labels <- function() { function() { text <- c( "**Various text boxes in different stylings**", "Some text **in bold.**
(centered)", "Linebreaks
Linebreaks
Linebreaks", "*x*2 + 5*x* + *C*i
*a* = 5" ) x <- c(0, .4, .3, .8) y <- c(1, .8, .5, .3) rot <- c(0, 0, -45, 45) gp = gpar(col = c("black", "red")) box_gp = gpar(col = "black", fill = c(NA, "cornsilk", NA, "lightblue1"), lty = c(0, 1, 1, 1)) hjust <- c(0, 0.5, 0, 1) vjust <- c(1, 0.5, 1, 0) g <- richtext_grob( text, x, y, hjust = hjust, vjust = vjust, rot = rot, padding = unit(c(6, 6, 4, 6), "pt"), r = unit(c(0, 0, 4, 8), "pt"), gp = gp, box_gp = box_gp ) grid.draw(g) grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) invisible() } } expect_doppelganger("Various text boxes", draw_labels()) draw_labels_debug <- function() { function() { text <- c( "Some text **in bold.**
(centered)", "Linebreaks
Linebreaks
Linebreaks", "*x*2 + 5*x* + *C*i
*a* = 5" ) x <- c(.4, .3, .8) y <- c(.8, .5, .3) rot <- c(0, -45, 45) gp = gpar(col = c("black", "red", "black")) box_gp = gpar(col = "black", fill = c("cornsilk", NA, "lightblue1"), lty = c(1, 1, 1)) hjust <- c(0.5, 0, 1) vjust <- c(0.5, 1, 0) g <- richtext_grob( text, x, y, hjust = hjust, vjust = vjust, rot = rot, padding = unit(c(6, 6, 4, 6), "pt"), r = unit(c(0, 4, 8), "pt"), gp = gp, box_gp = box_gp, debug = TRUE ) grid.draw(g) grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) invisible() } } expect_doppelganger("Various text boxes w/ debug", draw_labels_debug()) draw_aligned_heights <- function() { function() { text <- c( "Some text **in bold.**
(centered)", "Linebreaks
Linebreaks
Linebreaks", "*x*2 + 5*x* + *C*i
*a* = 5" ) x <- c(.4, .3, .8) y <- c(.8, .5, .3) rot <- c(0, -45, 45) gp = gpar() box_gp = gpar(col = "black", fill = c("cornsilk", NA, "lightblue1")) hjust <- c(0.5, 0, 1) vjust <- c(0.5, 1, 0) g <- richtext_grob( text, x, y, halign = 0.5, valign = 0.5, hjust = hjust, vjust = vjust, rot = rot, align_heights = TRUE, padding = unit(c(6, 6, 4, 6), "pt"), r = unit(c(0, 4, 8), "pt"), gp = gp, box_gp = box_gp ) grid.draw(g) grid.text("Box heights aligned, content centered", gp = gpar(fontface = "bold"), 0.02, 1, hjust = 0, vjust = 1.2) grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) invisible() } } expect_doppelganger("Aligned heights", draw_aligned_heights()) draw_aligned_widths <- function() { function() { text <- c( "Some text **in bold.**
(centered)", "Linebreaks
Linebreaks
Linebreaks", "*x*2 + 5*x* + *C*i
*a* = 5" ) x <- c(.4, .3, .8) y <- c(.8, .5, .3) rot <- c(0, -45, 45) gp = gpar() box_gp = gpar(col = "black", fill = c("cornsilk", NA, "lightblue1")) hjust <- c(0.5, 0, 1) vjust <- c(0.5, 1, 0) g <- richtext_grob( text, x, y, halign = 0.5, valign = 0.5, hjust = hjust, vjust = vjust, rot = rot, align_widths = TRUE, padding = unit(c(6, 6, 4, 6), "pt"), r = unit(c(0, 4, 8), "pt"), gp = gp, box_gp = box_gp ) grid.draw(g) grid.text("Box widths aligned, content centered", gp = gpar(fontface = "bold"), 0.02, 1, hjust = 0, vjust = 1.2) grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) invisible() } } expect_doppelganger("Aligned widths", draw_aligned_widths()) }) gridtext/tests/testthat/test-vbox.R0000644000176200001440000000676113757770565017224 0ustar liggesuserstest_that("vertical stacking works", { nb <- bl_make_null_box() rb1 <- bl_make_rect_box(nb, 100, 100, rep(0, 4), rep(0, 4), gp = gpar()) rb2 <- bl_make_rect_box(nb, 50, 50, rep(10, 4), rep(0, 4), gp = gpar()) rb3 <- bl_make_rect_box(nb, 50, 10, rep(0, 4), rep(0, 4), gp = gpar(), width_policy = "expand") vb <- bl_make_vbox(list(rb1, rb2, rb3), width = 200, hjust = 0, vjust = 0, width_policy = "fixed") bl_calc_layout(vb, 0, 0) bl_place(vb, 0, 0) expect_identical(bl_box_width(vb), 200) expect_identical(bl_box_height(vb), 160) expect_identical(bl_box_voff(vb), 0) g <- bl_render(vb, 200, 100) out1 <- g[[1]] expect_identical(out1$x, unit(200, "pt")) expect_identical(out1$y, unit(100 + 10 + 50, "pt")) out2 <- g[[2]] expect_identical(out2$x, unit(210, "pt")) expect_identical(out2$y, unit(100 + 10 + 10, "pt")) out3 <- g[[3]] expect_identical(out3$x, unit(200, "pt")) expect_identical(out3$y, unit(100, "pt")) expect_identical(out3$width, unit(200, "pt")) # alternatve hjust, vjust, x, y vb <- bl_make_vbox(list(rb1, rb2, rb3), width = 200, hjust = 1, vjust = 1, width_policy = "fixed") bl_calc_layout(vb, 0, 0) bl_place(vb, 15, 27) expect_identical(bl_box_width(vb), 200) expect_identical(bl_box_height(vb), 160) g <- bl_render(vb, 200, 100) out1 <- g[[1]] expect_identical(out1$x, unit(15 + 0, "pt")) expect_identical(out1$y, unit(27 - 60 + 10 + 50, "pt")) out2 <- g[[2]] expect_identical(out2$x, unit(15 + 10, "pt")) expect_identical(out2$y, unit(27 - 60 + 10 + 10, "pt")) out3 <- g[[3]] expect_identical(out3$x, unit(15 + 0, "pt")) expect_identical(out3$y, unit(27 - 60, "pt")) expect_identical(out3$width, unit(200, "pt")) }) test_that("size policies", { nb <- bl_make_null_box() rb1 <- bl_make_rect_box(nb, 100, 100, rep(0, 4), rep(0, 4), gp = gpar()) rb2 <- bl_make_rect_box(nb, 50, 50, rep(10, 4), rep(0, 4), gp = gpar()) vb <- bl_make_vbox(list(rb1, rb2), width = 200, hjust = 0.5, vjust = 0.5, width_policy = "native") bl_calc_layout(vb, 0, 0) expect_identical(bl_box_width(vb), 100) expect_identical(bl_box_height(vb), 150) vb <- bl_make_vbox(list(rb1, rb2), width = 200, hjust = 0.5, vjust = 0.5, width_policy = "relative") bl_calc_layout(vb, 70, 0) expect_identical(bl_box_width(vb), 140) expect_identical(bl_box_height(vb), 150) vb <- bl_make_vbox(list(rb1, rb2), width = 200, hjust = 0.5, vjust = 0.5, width_policy = "expand") bl_calc_layout(vb, 300, 0) expect_identical(bl_box_width(vb), 300) expect_identical(bl_box_height(vb), 150) }) test_that("vertical offset is ignored in vertical stacking", { tb1 <- bl_make_text_box("string1", gp = gpar(fontsize = 10)) tb2 <- bl_make_text_box("string2", gp = gpar(fontsize = 20), voff = -10) tb3 <- bl_make_text_box("string2", gp = gpar(fontsize = 20), voff = 0) tb4 <- bl_make_text_box("string3", gp = gpar(fontsize = 15)) vb1 <- bl_make_vbox(list(tb1, tb2, tb4), hjust = 0, vjust = 0) bl_calc_layout(vb1, 100, 100) bl_place(vb1, 17, 24) vb2 <- bl_make_vbox(list(tb1, tb3, tb4), hjust = 0, vjust = 0) bl_calc_layout(vb2, 100, 100) bl_place(vb2, 17, 24) g1 <- bl_render(vb1, 0, 0) g2 <- bl_render(vb2, 0, 0) extract <- function(x, name) {x[[name]]} expect_identical( lapply(g1, extract, name = "x"), lapply(g2, extract, name = "x") ) expect_identical( lapply(g1, extract, name = "y"), lapply(g2, extract, name = "y") ) expect_identical( lapply(g1, extract, name = "label"), lapply(g2, extract, name = "label") ) }) gridtext/tests/testthat/Rplots.pdf0000644000176200001440000000736613764204764017115 0ustar liggesusers%PDF-1.4 %ρ\r 1 0 obj << /CreationDate (D:20201209113131) /ModDate (D:20201209113131) /Title (R Graphics Output) /Producer (R 4.0.2) /Creator (R) >> endobj 2 0 obj << /Type /Catalog /Pages 3 0 R >> endobj 7 0 obj << /Type /Page /Parent 3 0 R /Contents 8 0 R /Resources 4 0 R >> endobj 8 0 obj << /Length 21 /Filter /FlateDecode >> stream x3TR0TR( a.~Yendstream endobj 3 0 obj << /Type /Pages /Kids [ 7 0 R ] /Count 1 /MediaBox [0 0 504 504] >> endobj 4 0 obj << /ProcSet [/PDF /Text] /Font <<>> /ExtGState << >> /ColorSpace << /sRGB 5 0 R >> >> endobj 5 0 obj [/ICCBased 6 0 R] endobj 6 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~endstream endobj 9 0 obj << /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [ 45/minus 96/quoteleft 144/dotlessi /grave /acute /circumflex /tilde /macron /breve /dotaccent /dieresis /.notdef /ring /cedilla /.notdef /hungarumlaut /ogonek /caron /space] >> endobj xref 0 10 0000000000 65535 f 0000000021 00000 n 0000000163 00000 n 0000000383 00000 n 0000000466 00000 n 0000000567 00000 n 0000000600 00000 n 0000000212 00000 n 0000000292 00000 n 0000003295 00000 n trailer << /Size 10 /Info 1 0 R /Root 2 0 R >> startxref 3552 %%EOF gridtext/tests/testthat/test-grid-constructors.R0000644000176200001440000001227213757770504021724 0ustar liggesuserstest_that("unit_pt", { expect_equal( unit_pt(10), grid::unit(10, "pt") ) expect_identical( unit_pt(1:10), grid::unit(1:10, "pt") ) }) test_that("gpar_empty", { expect_identical( gpar_empty(), grid::gpar() ) }) test_that("text_grob", { # basic functionality, gp is set to gpar() if not provided expect_identical( text_grob("test", 10, 20, name = "abc"), textGrob( "test", x = unit(10, "pt"), y = unit(20, "pt"), hjust = 0, vjust = 0, gp = gpar(), name = "abc" ) ) # basic functionality, x and y are set to 0 if not provided expect_identical( text_grob("test", name = "abc"), textGrob( "test", x = unit(0, "pt"), y = unit(0, "pt"), hjust = 0, vjust = 0, gp = gpar(), name = "abc" ) ) # gp is set as requested gp <- gpar(col = "blue", fill = "red") expect_identical( text_grob("test", 10, 20, gp = gp, name = "abc"), textGrob( "test", x = unit(10, "pt"), y = unit(20, "pt"), hjust = 0, vjust = 0, gp = gp, name = "abc" ) ) # if no name is provided, different names are assigned g1 <- text_grob("test") g2 <- text_grob("test") expect_false(identical(g1$name, g2$name)) # function is not vectorized expect_error( text_grob(c("test", "test"), 10, 20), "not vectorized" ) expect_error( text_grob("test", 1:5, 20), "not vectorized" ) expect_error( text_grob("test", 10, 1:5), "not vectorized" ) # arguments of length 0 are also disallowed expect_error( text_grob("test", numeric(0), 5), "not vectorized" ) }) test_that("raster_grob", { # basic functionality image <- matrix(0:1, ncol = 5, nrow = 4) expect_identical( raster_grob(image, 10, 20, 50, 40, gp = gpar(), name = "abc"), rasterGrob( image, x = unit(10, "pt"), y = unit(20, "pt"), width = unit(50, "pt"), height = unit(40, "pt"), hjust = 0, vjust = 0, interpolate = TRUE, gp = gpar(), name = "abc" ) ) # interpolate is set as requested, gp default is NULL expect_identical( raster_grob(image, 10, 20, 50, 40, interpolate = FALSE, name = "abc"), rasterGrob( image, x = unit(10, "pt"), y = unit(20, "pt"), width = unit(50, "pt"), height = unit(40, "pt"), hjust = 0, vjust = 0, interpolate = FALSE, gp = NULL, name = "abc" ) ) # if no name is provided, different names are assigned g1 <- raster_grob(image) g2 <- raster_grob(image) expect_false(identical(g1$name, g2$name)) # function is not vectorized expect_error( raster_grob(image, c(10, 20), 20, 100, 140), "not vectorized" ) expect_error( raster_grob(image, 10, numeric(0), 100, 140), "not vectorized" ) }) test_that("rect_grob", { # basic functionality, gp is set to gpar() if not provided expect_identical( rect_grob(10, 20, 100, 140, name = "abc"), rectGrob( x = unit(10, "pt"), y = unit(20, "pt"), width = unit(100, "pt"), height = unit(140, "pt"), hjust = 0, vjust = 0, gp = gpar(), name = "abc" ) ) # gp is set as requested gp <- gpar(col = "blue", fill = "red") expect_identical( rect_grob(10, 20, 100, 140, gp = gp, name = "abc"), rectGrob( x = unit(10, "pt"), y = unit(20, "pt"), width = unit(100, "pt"), height = unit(140, "pt"), hjust = 0, vjust = 0, gp = gp, name = "abc" ) ) # if no name is provided, different names are assigned g1 <- rect_grob() g2 <- rect_grob() expect_false(identical(g1$name, g2$name)) # function is not vectorized expect_error( rect_grob(c(10, 20), 20, 100, 140), "not vectorized" ) expect_error( rect_grob(10, numeric(0), 100, 140), "not vectorized" ) }) test_that("roundrect_grob", { # basic functionality, gp is set to gpar() if not provided expect_identical( roundrect_grob(10, 20, 100, 140, 10, name = "abc"), roundrectGrob( x = unit(10, "pt"), y = unit(20, "pt"), width = unit(100, "pt"), height = unit(140, "pt"), r = unit(10, "pt"), just = c(0, 0), gp = gpar(), name = "abc" ) ) # gp is set as requested gp <- gpar(col = "blue", fill = "red") expect_identical( roundrect_grob(10, 20, 100, 140, 20, gp = gp, name = "abc"), roundrectGrob( x = unit(10, "pt"), y = unit(20, "pt"), width = unit(100, "pt"), height = unit(140, "pt"), r = unit(20, "pt"), just = c(0, 0), gp = gp, name = "abc" ) ) # if no name is provided, different names are assigned g1 <- roundrect_grob() g2 <- roundrect_grob() expect_false(identical(g1$name, g2$name)) # function is not vectorized expect_error( roundrect_grob(c(10, 20), 20, 100, 140, 20), "not vectorized" ) expect_error( roundrect_grob(10, numeric(0), 100, 140, 20), "not vectorized" ) }) test_that("set_grob_coords", { g <- list(x = 0, y = 0) # setting coords as numbers expect_identical( set_grob_coords(g, x = 20, y = 40), list(x = 20, y = 40) ) # setting coords as units expect_identical( set_grob_coords(g, x = unit_pt(20), y = unit_pt(40)), list(x = unit_pt(20), y = unit_pt(40)) ) }) gridtext/tests/testthat/test-text-details.R0000644000176200001440000000307513757770552020644 0ustar liggesuserstest_that("text_details() calculates info correctly", { # descent and space are independent of string gp1 <- gpar(fontfamily = "Helvetica", fontface = "plain", fontsize = 10) t1 <- text_details("abcd", gp = gp1) t2 <- text_details("gjqp", gp = gp1) expect_equal(t1$descent_pt, t2$descent_pt) expect_equal(t1$space_pt, t2$space_pt) # recalculating the same details gives same results (tests caching) t2 <- text_details("abcd", gp = gp1) expect_equal(t1$width_pt, t2$width_pt) expect_equal(t1$ascent_pt, t2$ascent_pt) expect_equal(t1$descent_pt, t2$descent_pt) expect_equal(t1$space_pt, t2$space_pt) # all parameters scale with font size gp2 <- gpar(fontfamily = "Helvetica", fontface = "plain", fontsize = 20) t2 <- text_details("abcd", gp = gp2) expect_equal(2 * t1$width_pt, t2$width_pt) expect_equal(2 * t1$ascent_pt, t2$ascent_pt) expect_equal(2 * t1$descent_pt, t2$descent_pt) expect_equal(2 * t1$space_pt, t2$space_pt) # parameters change with font gp2 <- gpar(fontfamily = "Times", fontface = "plain", fontsize = 10) t2 <- text_details("abcd", gp = gp2) expect_false(t1$width_pt == t2$width_pt) expect_false(t1$ascent_pt == t2$ascent_pt) expect_false(t1$descent_pt == t2$descent_pt) expect_false(t1$space_pt == t2$space_pt) # font details are identical to what we would get from an actual grob g <- textGrob("Qbcd", gp = gp1) t1 <- text_details("Qbcd", gp = gp1) expect_equal(t1$ascent_pt, convertHeight(grobHeight(g), "pt", valueOnly = TRUE)) expect_equal(t1$width_pt, convertWidth(grobWidth(g), "pt", valueOnly = TRUE)) }) gridtext/tests/testthat/test-raster-box.R0000644000176200001440000001236113757770530020315 0ustar liggesuserstest_that("image dimensions are used", { logo_file <- system.file("extdata", "Rlogo.png", package = "gridtext") logo <- png::readPNG(logo_file, native = FALSE) # default size policy is native for both height and width # dpi = 72.27 turns lengths in pixels to lengths in pt rb <- bl_make_raster_box(logo, dpi = 72.27) bl_calc_layout(rb, 100, 100) bl_place(rb, 30, 5) g <- bl_render(rb, 10, 20) img <- g[[1]] expect_identical(img$x, unit(40, "pt")) expect_identical(img$y, unit(25, "pt")) expect_equal(img$width, unit(ncol(logo), "pt")) expect_equal(img$height, unit(nrow(logo), "pt")) # test now with raster object logo2 <- as.raster(logo) rb <- bl_make_raster_box(logo2, dpi = 72.27) bl_calc_layout(rb, 100, 100) g <- bl_render(rb, 10, 20) img <- g[[1]] expect_identical(img$x, unit(10, "pt")) expect_identical(img$y, unit(20, "pt")) expect_equal(img$width, unit(ncol(logo), "pt")) expect_equal(img$height, unit(nrow(logo), "pt")) # test now with nativeRaster object logo3 <- png::readPNG(logo_file, native = TRUE) rb <- bl_make_raster_box(logo3, dpi = 72.27) bl_calc_layout(rb, 100, 100) g <- bl_render(rb, 10, 20) img <- g[[1]] expect_identical(img$x, unit(10, "pt")) expect_identical(img$y, unit(20, "pt")) expect_equal(img$width, unit(ncol(logo), "pt")) expect_equal(img$height, unit(nrow(logo), "pt")) # dimensions are reported correctly expect_equal(bl_box_width(rb), ncol(logo)) expect_equal(bl_box_height(rb), nrow(logo)) expect_equal(bl_box_ascent(rb), nrow(logo)) expect_identical(bl_box_descent(rb), 0) expect_identical(bl_box_voff(rb), 0) m <- 1:10 dim(m) <- 10 expect_error( bl_make_raster_box(m), "Cannot extract image dimensions." ) }) test_that("size policies, respect_aspect = FALSE", { logo_file <- system.file("extdata", "Rlogo.png", package = "gridtext") logo <- png::readPNG(logo_file, native = TRUE) rb <- bl_make_raster_box(logo, width = 50, height = 80, width_policy = "fixed", height_policy = "fixed", respect_aspect = FALSE) bl_calc_layout(rb, 200, 100) g <- bl_render(rb, 10, 20) img <- g[[1]] expect_identical(img$x, unit(10, "pt")) expect_identical(img$y, unit(20, "pt")) expect_identical(img$width, unit(50, "pt")) expect_identical(img$height, unit(80, "pt")) rb <- bl_make_raster_box(logo, width = 50, height = 80, width_policy = "relative", height_policy = "expand", respect_aspect = FALSE) bl_calc_layout(rb, 200, 100) g <- bl_render(rb, 10, 20) img <- g[[1]] expect_identical(img$x, unit(10, "pt")) expect_identical(img$y, unit(20, "pt")) expect_identical(img$width, unit(100, "pt")) expect_identical(img$height, unit(100, "pt")) rb <- bl_make_raster_box(logo, width = 50, height = 80, width_policy = "expand", height_policy = "relative", respect_aspect = FALSE) bl_calc_layout(rb, 200, 100) g <- bl_render(rb, 10, 20) img <- g[[1]] expect_identical(img$x, unit(10, "pt")) expect_identical(img$y, unit(20, "pt")) expect_identical(img$width, unit(200, "pt")) expect_identical(img$height, unit(80, "pt")) rb <- bl_make_raster_box(logo, width = 50, height = 80, width_policy = "fixed", height_policy = "native", respect_aspect = FALSE) bl_calc_layout(rb, 200, 100) g <- bl_render(rb, 10, 20) img <- g[[1]] expect_identical(img$x, unit(10, "pt")) expect_identical(img$y, unit(20, "pt")) expect_identical(img$width, unit(50, "pt")) expect_equal(img$height, unit(50*nrow(logo)/ncol(logo), "pt")) rb <- bl_make_raster_box(logo, width = 50, height = 80, width_policy = "native", height_policy = "fixed", respect_aspect = FALSE) bl_calc_layout(rb, 200, 100) g <- bl_render(rb, 10, 20) img <- g[[1]] expect_identical(img$x, unit(10, "pt")) expect_identical(img$y, unit(20, "pt")) expect_equal(img$width, unit(80*ncol(logo)/nrow(logo), "pt")) expect_identical(img$height, unit(80, "pt")) }) test_that("size policies, respect_aspect = TRUE", { logo_file <- system.file("extdata", "Rlogo.png", package = "gridtext") logo <- png::readPNG(logo_file, native = TRUE) rb <- bl_make_raster_box(logo, width = 50, height = 80, width_policy = "fixed", height_policy = "fixed") bl_calc_layout(rb, 200, 100) g <- bl_render(rb, 10, 20) nr <- nrow(logo) nc <- ncol(logo) img_height <- 50*nr/nc yoff <- (80 - img_height)/2 img <- g[[1]] expect_identical(img$x, unit(10, "pt")) expect_equal(img$y, unit(20 + yoff, "pt")) expect_identical(img$width, unit(50, "pt")) expect_equal(img$height, unit(img_height, "pt")) rb <- bl_make_raster_box(logo, width = 80, height = 50, width_policy = "fixed", height_policy = "fixed") bl_calc_layout(rb, 200, 100) g <- bl_render(rb, 10, 20) nr <- nrow(logo) nc <- ncol(logo) img_width <- 50*nc/nr xoff <- (80 - img_width)/2 img <- g[[1]] expect_equal(img$x, unit(10 + xoff, "pt")) expect_identical(img$y, unit(20, "pt")) expect_equal(img$width, unit(img_width, "pt")) expect_identical(img$height, unit(50, "pt")) }) gridtext/tests/testthat/test-grid-renderer.R0000644000176200001440000000721113764204735020753 0ustar liggesuserscontext("grid-renderer") test_that("basic functioning", { r <- grid_renderer() g <- grid_renderer_collect_grobs(r) # without any grobs rendered, we get an empty list of class gList expect_equal(length(g), 0) expect_true(inherits(g, "gList")) # grobs get added in order grid_renderer_text(r, "abcd", 100, 100, gpar()) grid_renderer_rect(r, 100, 100, 200, 200, gpar()) g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 2) expect_true(inherits(g, "gList")) expect_true(inherits(g[[1]], "text")) expect_true(inherits(g[[2]], "rect")) # internal state gets reset after calling collect_grobs() g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 0) expect_true(inherits(g, "gList")) }) test_that("smart rendering of rects", { r <- grid_renderer() # add normal rect grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar()) # add rect with rounded corners grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar(), r = 5) # add rect that is invisible, gets removed automatically grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar(col = NA)) g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 2) expect_true(inherits(g, "gList")) expect_true(inherits(g[[1]], "rect")) expect_true(inherits(g[[2]], "roundrect")) # more extensive testing variations for dropping unneeded rects grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar(lty = 0)) g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 0) grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar(col = NA, lty = 1)) g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 0) grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar(col = "black", lty = 0)) g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 0) grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar(fill = NA, lty = 0)) g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 0) grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar(fill = NA, col = "black", lty = 0)) g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 0) grid_renderer_rect(r, 100, 100, 200, 200, gp = gpar(fill = NA, col = NA, lty = 1)) g <- grid_renderer_collect_grobs(r) expect_equal(length(g), 0) }) test_that("visual tests", { draw_grob <- function(g) { function() { grid.newpage() grid.draw(g) invisible() } } r <- grid_renderer() grid_renderer_text(r, "blue", 10, 400, gp = gpar(col = "blue", fontsize = 12)) grid_renderer_text(r, "red bold", 20, 380, gp = gpar(col = "red", fontsize = 12, fontface = "bold")) grid_renderer_text(r, "roman", 30, 360, gp = gpar(fontsize = 12, fontfamily = "Times")) g <- grid_renderer_collect_grobs(r) expect_doppelganger("Text in different stylings", draw_grob(g)) grid_renderer_rect(r, 100, 400, 200, 20, gp = gpar(col = "blue")) grid_renderer_rect(r, 100, 200, 300, 30, gp = gpar(fill = "cornsilk"), r = 8) grid_renderer_text(r, "text 1, square box blue", 100, 400, gp = gpar(fontsize = 20)) grid_renderer_text(r, "text 2, rounded box filled", 100, 200, gp = gpar(fontsize = 20)) g <- grid_renderer_collect_grobs(r) expect_doppelganger("Mixing text and boxes", draw_grob(g)) logo_file <- system.file("extdata", "Rlogo.png", package = "gridtext") logo <- png::readPNG(logo_file, native = TRUE) width <- ncol(logo) height <- nrow(logo) grid_renderer_raster(r, logo, 10, 10, width, height) g <- grid_renderer_collect_grobs(r) expect_doppelganger("Rendering raster data", draw_grob(g)) }) test_that("text details are calculated correctly", { gp = gpar(fontsize = 20) td <- text_details("abcd", gp) td2 <- grid_renderer_text_details("abcd", gp) expect_identical(td, td2) }) gridtext/tests/figs/0000755000176200001440000000000013757774154014222 5ustar liggesusersgridtext/tests/figs/deps.txt0000644000176200001440000000010313757774114015704 0ustar liggesusers- vdiffr-svg-engine: 1.0 - vdiffr: 0.3.3 - freetypeharfbuzz: 0.2.5 gridtext/tests/figs/grid-renderer/0000755000176200001440000000000013757774114016747 5ustar liggesusersgridtext/tests/figs/grid-renderer/rendering-raster-data.svg0000644000176200001440000004744313757774114023666 0ustar liggesusers gridtext/tests/figs/grid-renderer/text-in-different-stylings.svg0000644000176200001440000000171713757774107024706 0ustar liggesusers blue red bold roman gridtext/tests/figs/grid-renderer/mixing-text-and-boxes.svg0000644000176200001440000000326613757774112023630 0ustar liggesusers text 1, square box blue text 2, rounded box filled gridtext/tests/figs/richtext-grob/0000755000176200001440000000000013757773775017013 5ustar liggesusersgridtext/tests/figs/richtext-grob/various-text-boxes.svg0000644000176200001440000001567013757773766023335 0ustar liggesusers Various text boxes in different stylings Some text in bold. (centered) Linebreaks Linebreaks Linebreaks x 2 + 5 x + C i a = 5 gridtext/tests/figs/richtext-grob/aligned-heights.svg0000644000176200001440000001347713757773773022602 0ustar liggesusers Some text in bold. (centered) Linebreaks Linebreaks Linebreaks x 2 + 5 x + C i a = 5 Box heights aligned, content centered gridtext/tests/figs/richtext-grob/various-text-boxes-w-debug.svg0000644000176200001440000001415113757773770024651 0ustar liggesusers Some text in bold. (centered) Linebreaks Linebreaks Linebreaks x 2 + 5 x + C i a = 5 gridtext/tests/figs/richtext-grob/aligned-widths.svg0000644000176200001440000001347613757773775022452 0ustar liggesusers Some text in bold. (centered) Linebreaks Linebreaks Linebreaks x 2 + 5 x + C i a = 5 Box widths aligned, content centered gridtext/tests/figs/textbox-grob/0000755000176200001440000000000013757773567016655 5ustar liggesusersgridtext/tests/figs/textbox-grob/multiple-boxes-internal-alignment.svg0000644000176200001440000001421413757773556026135 0ustar liggesusers The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. gridtext/tests/figs/textbox-grob/box-spanning-entire-viewport-with-margins.svg0000644000176200001440000001576713757773552027563 0ustar liggesusers The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. Box spanning entire viewport, with margins gridtext/tests/figs/textbox-grob/multiple-boxes-inverted-internal-alignment.svg0000644000176200001440000001745713757773565027767 0ustar liggesusers The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. gridtext/tests/figs/textbox-grob/rotation-around-fixed-point.svg0000644000176200001440000001701513757773567024753 0ustar liggesusers The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. gridtext/tests/figs/textbox-grob/multiple-boxes-right-rotated-internal-alignment.svg0000644000176200001440000001732513757773562030713 0ustar liggesusers The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. gridtext/tests/figs/textbox-grob/multiple-boxes-left-rotated-internal-alignment.svg0000644000176200001440000001743213757773560030525 0ustar liggesusers The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. gridtext/tests/testthat.R0000644000176200001440000000011213503046254015225 0ustar liggesuserslibrary(testthat) library(grid) library(gridtext) test_check("gridtext") gridtext/src/0000755000176200001440000000000013764502420012676 5ustar liggesusersgridtext/src/rect-box.h0000644000176200001440000002003113522707652014574 0ustar liggesusers#ifndef RECT_BOX_H #define RECT_BOX_H #include using namespace Rcpp; #include "layout.h" /* The RectBox class draws a box with margin and padding around * a box with content. The RectBox can either adjust its size * so that the content fits snugly (size policy "native") or its * size can be prespecified. In the latter case, the content box can * be aligned relative to the RectBox. The reference point of * the RectBox is the lower left corner. */ template class RectBox : public Box { private: BoxPtr m_content; // any layout node to be placed inside of the rectangle Length m_width, m_height; // width and height of the rectangle box (*not the inside*) Margin m_margin, m_padding; // margin and padding typename Renderer::GraphicsContext m_gp; double m_content_hjust, m_content_vjust; // horzontal and vertical justification for content inside the box SizePolicy m_width_policy, m_height_policy; // width and height policies Length m_r; // radius of rounded corners // position of the box in enclosing box. // the box reference point is the leftmost point of the baseline. Length m_x, m_y; double m_rel_width, m_rel_height; // used to store relative width and height when needed // layout calculation when width is defined (doesn't depend on content box) void calc_layout_defined_width(Length width_hint, Length height_hint) { // width policy is not `native` switch(m_width_policy) { case SizePolicy::expand: m_width = width_hint; break; case SizePolicy::relative: m_width = width_hint * m_rel_width; break; case SizePolicy::fixed: default: // nothing to be done for fixed layout, width was set upon creation break; } // if height policy is `native`, we need to layout the content box // before deciding on the height if (m_height_policy == SizePolicy::native) { if (!m_content) { // content is empty, nothing to layout m_height = m_margin.top + m_margin.bottom + m_padding.top + m_padding.bottom; } else { Length content_width_hint = m_width - m_margin.left - m_margin.right - m_padding.left - m_padding.right; Length content_height_hint = height_hint - m_margin.top - m_margin.bottom - m_padding.top - m_padding.bottom; m_content->calc_layout(content_width_hint, content_height_hint); m_height = m_content->height() + m_margin.top + m_margin.bottom + m_padding.top + m_padding.bottom; } } else { // height is also defined switch(m_height_policy) { case SizePolicy::expand: m_height = height_hint; break; case SizePolicy::relative: m_height = height_hint * m_rel_height; break; case SizePolicy::fixed: default: // nothing to be done for fixed layout, height was set upon creation break; } // if we have content, we still need to layout it if (m_content) { Length content_width_hint = m_width - m_margin.left - m_margin.right - m_padding.left - m_padding.right; Length content_height_hint = m_height - m_margin.top - m_margin.bottom - m_padding.top - m_padding.bottom; m_content->calc_layout(content_width_hint, content_height_hint); } } } // layout calculation when width is native void calc_layout_native_width(Length width_hint, Length height_hint) { // if height policy is `native`, we need to layout the content box // before deciding on the height if (m_height_policy == SizePolicy::native) { if (!m_content) { // content is empty, nothing to layout m_width = m_margin.left + m_margin.right + m_padding.left + m_padding.right; m_height = m_margin.top + m_margin.bottom + m_padding.top + m_padding.bottom; } else { Length content_width_hint = width_hint - m_margin.left - m_margin.right - m_padding.left - m_padding.right; Length content_height_hint = height_hint - m_margin.top - m_margin.bottom - m_padding.top - m_padding.bottom; m_content->calc_layout(content_width_hint, content_height_hint); m_width = m_content->width() + m_margin.left + m_margin.right + m_padding.left + m_padding.right; m_height = m_content->height() + m_margin.top + m_margin.bottom + m_padding.top + m_padding.bottom; } } else { // height is defined switch(m_height_policy) { case SizePolicy::expand: m_height = height_hint; break; case SizePolicy::relative: m_height = height_hint * m_rel_height; break; case SizePolicy::fixed: default: // nothing to be done for fixed layout, height was set upon creation break; } // height is set, but we still need to set width if (!m_content) { // content is empty, nothing to layout m_width = m_margin.left + m_margin.right + m_padding.left + m_padding.right; } else { Length content_width_hint = width_hint - m_margin.left - m_margin.right - m_padding.left - m_padding.right; Length content_height_hint = m_height - m_margin.top - m_margin.bottom - m_padding.top - m_padding.bottom; m_content->calc_layout(content_width_hint, content_height_hint); m_width = m_content->width() + m_margin.left + m_margin.right + m_padding.left + m_padding.right; } } } public: RectBox(const BoxPtr &content, Length width, Length height, const Margin &margin, const Margin &padding, const typename Renderer::GraphicsContext &gp, double content_hjust = 0, double content_vjust = 1, SizePolicy width_policy = SizePolicy::native, SizePolicy height_policy = SizePolicy::native, Length r = 0) : m_content(content), m_width(width), m_height(height), m_margin(margin), m_padding(padding), m_gp(gp), m_content_hjust(content_hjust), m_content_vjust(content_vjust), m_width_policy(width_policy), m_height_policy(height_policy), m_r(r), m_x(0), m_y(0), m_rel_width(0), m_rel_height(0) { // save relative width and height if needed if (m_width_policy == SizePolicy::relative) { m_rel_width = m_width/100; } if (m_height_policy == SizePolicy::relative) { m_rel_height = m_height/100; } } ~RectBox() {}; Length width() { return m_width; } Length ascent() { return m_height; } Length descent() { return 0; } Length voff() { return 0; } void calc_layout(Length width_hint, Length height_hint) { if (m_width_policy == SizePolicy::native) { calc_layout_native_width(width_hint, height_hint); } else { calc_layout_defined_width(width_hint, height_hint); } // after layouting, we need to place the content if we have some if (m_content) { Length x_align = m_content_hjust * (m_width - m_margin.left - m_margin.right - m_padding.left - m_padding.right // available internal space - m_content->width()); // actual space needed Length y_align = m_content_vjust * (m_height - m_margin.top - m_margin.bottom - m_padding.top - m_padding.bottom // available internal space - m_content->height()); // actual space needed // we place the content relative to the lower left corner of the interior box // (ignoring the outer margins) m_content->place( m_padding.left + x_align, m_padding.bottom + y_align + m_content->descent() - m_content->voff() ); } } // place box in internal coordinates used in enclosing box void place(Length x, Length y) { m_x = x; m_y = y; } // render into absolute coordinates, using the reference coordinates // from the enclosing box void render(Renderer &r, Length xref, Length yref) { Length x = m_x + xref + m_margin.left; Length y = m_y + yref + m_margin.bottom; Length width = m_width - m_margin.left - m_margin.right; Length height = m_height - m_margin.bottom - m_margin.top; r.rect(x, y, width, height, m_gp, m_r); // if we have content we need to render it if (m_content) { m_content->render(r, x, y); } } }; #endif gridtext/src/null-box.h0000644000176200001440000000147313507756025014623 0ustar liggesusers#ifndef NULL_BOX_H #define NULL_BOX_H #include using namespace Rcpp; #include "layout.h" /* The NullBox draws nothing. If given a width or a height, * it can be used as a spacer. The reference point of * the NullBox is the lower left corner. */ template class NullBox : public Box { private: Length m_width; Length m_height; public: NullBox(Length width = 0, Length height = 0) : m_width(width), m_height(height) {} ~NullBox() {} Length width() { return m_width; } Length ascent() { return m_height; } Length descent() { return 0; } Length voff() { return 0; } // nothing to be done void calc_layout(Length, Length) {} // nothing to be done void place(Length, Length) {} // nothing to be done void render(Renderer &, Length, Length) {} }; #endif gridtext/src/vbox.h0000644000176200001440000000573713524143134014036 0ustar liggesusers#ifndef VBOX_H #define VBOX_H #include using namespace Rcpp; #include "layout.h" /* The VBox class takes a list of boxes and lays them out * horizontally, breaking lines if necessary. The reference point * is the lower left corner of the box. */ template class VBox : public Box { private: BoxList m_nodes; Length m_width; Length m_height; SizePolicy m_width_policy; // width policy; height policy is always "native" // reference point of the box Length m_x, m_y; // justification of box relative to reference Length m_hjust, m_vjust; double m_rel_width; // used to store relative width when needed public: VBox(const BoxList& nodes, Length width = 0, double hjust = 0, double vjust = 1, SizePolicy width_policy = SizePolicy::native) : m_nodes(nodes), m_width(width), m_height(0), m_width_policy(width_policy), m_x(0), m_y(0), m_hjust(hjust), m_vjust(vjust), m_rel_width(0) { if (m_width_policy == SizePolicy::relative) { m_rel_width = m_width/100; } } ~VBox() {}; Length width() { return m_width; } Length ascent() { return m_height; } Length descent() { return 0; } Length voff() { return 0; } void calc_layout(Length width_hint, Length height_hint) { switch(m_width_policy) { case SizePolicy::expand: m_width = width_hint; break; case SizePolicy::relative: m_width = width_hint * m_rel_width; width_hint = m_width; break; case SizePolicy::fixed: width_hint = m_width; break; case SizePolicy::native: default: // nothing to be done for native policy, will be handled below break; } // y offset as we layout Length y_off = 0; // calculated box width Length width = 0; for (auto i_node = m_nodes.begin(); i_node != m_nodes.end(); i_node++) { auto b = (*i_node); // we propagate width and height hints to all child nodes, // in case they are useful there b->calc_layout(width_hint, height_hint); y_off -= b->ascent(); // place node, ignoring any vertical offset from baseline // (we stack boxes vertically, baselines don't matter here) b->place(0, y_off - b->voff()); y_off -= b->descent(); // account for box descent if any // record width if (b->width() > width) { width = b->width(); } } if (m_width_policy == SizePolicy::native) { // we record the calculated width for native width policy // in all other cases, it's already set m_width = width; } m_height = -y_off; } void place(Length x, Length y) { m_x = x; m_y = y; } void render(Renderer &r, Length xref, Length yref) { // render all grobs in the list for (auto i_node = m_nodes.begin(); i_node != m_nodes.end(); i_node++) { (*i_node)->render( r, xref + m_x - m_hjust*m_width, yref + m_height + m_y - m_vjust*m_height ); } } }; #endif gridtext/src/grid.cpp0000644000176200001440000001161013624672536014341 0ustar liggesusers#include "grid.h" NumericVector unit_pt(NumericVector x) { // create unit vector by calling back to R Environment env = Environment::namespace_env("grid"); Function unit = env["unit"]; return unit(x, "pt"); } NumericVector unit_pt(Length x) { NumericVector out(1, x); return unit_pt(out); } List gpar_empty() { List out; out.attr("class") = "gpar"; return out; } List text_grob(CharacterVector label, NumericVector x_pt, NumericVector y_pt, RObject gp, RObject name) { if (label.size() != 1 || x_pt.size() != 1 || y_pt.size() != 1) { stop("Function text_grob() is not vectorized.\n"); } if (gp.isNULL()) { gp = gpar_empty(); } // need to produce a unique name for each grob, otherwise grid gets grumpy static int tg_count = 0; if (name.isNULL()) { tg_count += 1; string s("gridtext.text."); s = s + to_string(tg_count); CharacterVector vs; vs.push_back(s); name = vs; } List out = List::create( _["label"] = label, _["x"] = unit_pt(x_pt), _["y"] = unit_pt(y_pt), _["just"] = "centre", _["hjust"] = 0., _["vjust"] = 0., _["rot"] = 0., _["check.overlap"] = false, _["name"] = name, _["gp"] = gp, _["vp"] = R_NilValue ); Rcpp::StringVector cl(3); cl(0) = "text"; cl(1) = "grob"; cl(2) = "gDesc"; out.attr("class") = cl; return out; } List raster_grob(RObject image, NumericVector x_pt, NumericVector y_pt, NumericVector width_pt, NumericVector height_pt, LogicalVector interpolate, RObject gp, RObject name) { if (x_pt.size() != 1 || y_pt.size() != 1 || width_pt.size() != 1 || height_pt.size() != 1) { stop("Function raster_grob() is not vectorized.\n"); } // need to produce a unique name for each grob, otherwise grid gets grumpy static int tg_count = 0; if (name.isNULL()) { tg_count += 1; string s("gridtext.raster."); s = s + to_string(tg_count); CharacterVector vs; vs.push_back(s); name = vs; } RObject raster = image; if (!raster.inherits("nativeRaster")) { // convert to raster by calling grDevices::as.raster() Environment env = Environment::namespace_env("grDevices"); Function as_raster = env["as.raster"]; raster = as_raster(image); } List out = List::create( _["raster"] = raster, _["x"] = unit_pt(x_pt), _["y"] = unit_pt(y_pt), _["width"] = unit_pt(width_pt), _["height"] = unit_pt(height_pt), _["just"] = "centre", _["hjust"] = 0., _["vjust"] = 0., _["interpolate"] = interpolate, _["name"] = name, _["gp"] = gp, _["vp"] = R_NilValue ); Rcpp::StringVector cl(3); cl(0) = "rastergrob"; cl(1) = "grob"; cl(2) = "gDesc"; out.attr("class") = cl; return out; } List rect_grob(NumericVector x_pt, NumericVector y_pt, NumericVector width_pt, NumericVector height_pt, RObject gp, RObject name) { if (x_pt.size() != 1 || y_pt.size() != 1 || width_pt.size() != 1 || height_pt.size() != 1) { stop("Function rect_grob() is not vectorized.\n"); } if (gp.isNULL()) { gp = gpar_empty(); } // need to produce a unique name for each grob, otherwise grid gets grumpy static int tg_count = 0; if (name.isNULL()) { tg_count += 1; string s("gridtext.rect."); s = s + to_string(tg_count); CharacterVector vs; vs.push_back(s); name = vs; } List out = List::create( _["x"] = unit_pt(x_pt), _["y"] = unit_pt(y_pt), _["width"] = unit_pt(width_pt), _["height"] = unit_pt(height_pt), _["just"] = "centre", _["hjust"] = 0., _["vjust"] = 0., _["name"] = name, _["gp"] = gp, _["vp"] = R_NilValue ); Rcpp::StringVector cl(3); cl(0) = "rect"; cl(1) = "grob"; cl(2) = "gDesc"; out.attr("class") = cl; return out; } List roundrect_grob(NumericVector x_pt, NumericVector y_pt, NumericVector width_pt, NumericVector height_pt, NumericVector r_pt, RObject gp, RObject name) { if (x_pt.size() != 1 || y_pt.size() != 1 || width_pt.size() != 1 || height_pt.size() != 1 || r_pt.size() != 1) { stop("Function roundrect_grob() is not vectorized.\n"); } if (gp.isNULL()) { gp = gpar_empty(); } // need to produce a unique name for each grob, otherwise grid gets grumpy static int tg_count = 0; if (name.isNULL()) { tg_count += 1; string s("gridtext.roundrect."); s = s + to_string(tg_count); CharacterVector vs; vs.push_back(s); name = vs; } NumericVector justv(2); // c(0, 0) List out = List::create( _["x"] = unit_pt(x_pt), _["y"] = unit_pt(y_pt), _["width"] = unit_pt(width_pt), _["height"] = unit_pt(height_pt), _["r"] = unit_pt(r_pt), _["just"] = justv, _["name"] = name, _["gp"] = gp, _["vp"] = R_NilValue ); Rcpp::StringVector cl(3); cl(0) = "roundrect"; cl(1) = "grob"; cl(2) = "gDesc"; out.attr("class") = cl; return out; } RObject set_grob_coords(RObject grob, NumericVector x, NumericVector y) { as(grob)["x"] = x; as(grob)["y"] = y; return grob; } gridtext/src/line-breaker.h0000644000176200001440000004336113522035034015410 0ustar liggesusers#ifndef LINE_BREAKER_H #define LINE_BREAKER_H #include using namespace Rcpp; #include #include "layout.h" #include "glue.h" #include "penalty.h" // helper class to record start and end points of lines to render class LineBreakInfo { public: size_t start; // first node in the line size_t end; // one past the last node in the line double r; // adjustment ratio Length width; // width of the line LineBreakInfo(size_t _start, size_t _end, double _r, Length _width) : start(_start), end(_end), r(_r), width(_width) {} }; // naive line breaker template class LineBreaker { private: const BoxList &m_nodes; const vector &m_line_lengths; bool m_word_wrap; // do we break at any feasible position or only at forced positions? vector m_sum_widths; // get width of node i Length get_width(size_t i) { if (i >= m_nodes.size()) { return 0; } auto node = m_nodes[i]; auto type = node->type(); if (type == NodeType::box) { return node->width(); } else if (type == NodeType::glue) { return static_cast*>(node.get())->default_width(); } else { // penalties have width 0 unless they get rendered return 0; } } // measure width from point a to point b, excluding b Length measure_width(size_t a, size_t b) { return m_sum_widths[b] - m_sum_widths[a]; } // calculate the length of the current line Length line_length(size_t line) { if (line < m_line_lengths.size()) { return m_line_lengths[line]; } else { return m_line_lengths.back(); } } // determine whether we can break at position i bool is_feasible_breakpoint(size_t i) { // if word wrap is off, only forced breaks are feasible breaks if (!m_word_wrap) { return is_forced_break(i); } // if we have run out of nodes we definitely want to break if (i >= m_nodes.size()) { return true; } // we can break at position i if either i is a penalty less than infinity // or if it is a glue and the previous node is a box auto node = m_nodes[i]; if (node->type() == NodeType::penalty) { if (static_cast*>(node.get())->penalty() < Penalty::infinity) { return true; } } else if (i > 0 && node->type() == NodeType::glue) { if (m_nodes[i-1]->type() == NodeType::box) { return true; } } return false; } // determine whether we must break at position i bool is_forced_break(size_t i) { // if we have run out of nodes we definitely want to break if (i >= m_nodes.size()) { return true; } // a penalty of -infinity is a forced break auto node = m_nodes[i]; if (node->type() == NodeType::penalty) { if (static_cast*>(node.get())->penalty() <= -1*Penalty::infinity) { return true; } } return false; } // determine whether we remove this node at the beginning of a line bool is_removable_whitespace(size_t i) { if (i >= m_nodes.size()) { return false; } auto node = m_nodes[i]; auto type = node->type(); if (type == NodeType::penalty) { // we cannot remove a forced break if (static_cast*>(node.get())->penalty() <= -1*Penalty::infinity) { return false; } else { return true; } } else if (type == NodeType::glue) { return true; } return false; } // advances i until the next possible point to start a line; used to // skip penalties and glue at beginning of a line size_t find_next_startpoint(size_t i) { while (i < m_nodes.size() && is_removable_whitespace(i)) { i++; } return i; } // advances i until the next feasible breakpoint size_t find_next_feasible_breakpoint(size_t i) { while (i < m_nodes.size() && !is_feasible_breakpoint(i)) { i++; } return i; } // to write unit tests that have access to private members friend class TestLineBreaker; public: LineBreaker(const BoxList& nodes, const vector &line_lengths, bool word_wrap = true) : m_nodes(nodes), m_line_lengths(line_lengths), m_word_wrap(word_wrap) { // calculate sums of widths size_t m = m_nodes.size(); m_sum_widths.resize(m + 1); Length running_sum_w = 0; for (size_t i = 0; i < m + 1; i++) { m_sum_widths[i] = running_sum_w; running_sum_w += get_width(i); } } void compute_line_breaks(vector &line_breaks) { line_breaks.clear(); // this is how we return the results; hence, clear first size_t a = 0; // starting point of the current line size_t line = 0; // current line we are processing while (a < m_nodes.size()) { //cout << "start" << " " << a << " " << m_nodes.size() << endl; a = find_next_startpoint(a); // skip whitespace at beginning of line size_t b = find_next_feasible_breakpoint(a); Length width = measure_width(a, b); // calculate width from a to b, excluding b Length linelen = line_length(line); // at a minimum, the current line contains material from a to b; however, if // b is not a forced break and the next piece fits, we can add it while (b < m_nodes.size() && !is_forced_break(b)) { //cout << "end" << " " << b << " " << m_nodes.size() << endl; size_t b_new = find_next_feasible_breakpoint(b + 1); Length width_delta = measure_width(b, b_new); // does the next piece fit? if (width + width_delta < linelen) { // yes, continue width += width_delta; b = b_new; } else { // no, exit inner loop break; } } // now we have a line from a to b // but place only if we're not past the end of the nodes list if (a < m_nodes.size()) { line_breaks.emplace_back(a, b, 0, width); //cout << line << " " << a << " " << b << endl; line++; // if b is a forced break, we need to advance by 1 to make sure // the break penalty gets skipped in the next line if (is_forced_break(b)) { b++; } a = b; } else { break; // exit outer loop, we're done } } } }; /*************************************************************** This code was never fully implemented and is currently disabled. #include #include #include #include #include using namespace std; // The LineBreaker class implements the Knuth-Plass algorithm, as described in // Knuth & Plass 1981 // support class representing an active breakpoint struct Breakpoint { size_t position, line; int fitness_class; // these are in the original paper but are not needed //Length totalwidth, totalstretch, totalshrink; int demerits; Breakpoint* previous; Breakpoint(size_t _position, size_t _line, int _fitness_class, //Length _totalwidth, Length _totalstretch, Length _totalshrink, int _demerits, Breakpoint* _previous = nullptr) : position(_position), line(_line), fitness_class(_fitness_class), //totalwidth(_totalwidth), totalstretch(_totalstretch), totalshrink(_totalshrink), demerits(_demerits), previous(_previous) {}; }; template class LineBreaker { private: const BoxList &m_nodes; const vector &m_line_lengths; double m_tolerance; double m_fitness_demerit, m_flagged_demerit; // vectors w, y, z, p, f as in the paper vector m_w, m_y, m_z; vector m_p; vector m_f; // sums of widths, stretch, and shrink vector m_sum_widths, m_sum_stretch, m_sum_shrink; // active and passive nodes list> m_active_nodes; vector> m_passive_nodes; void add_active_node(unique_ptr node) { // find the first position at which the line number of the new node // exceeds the line number of the node in the list auto i_node = m_active_nodes.begin(); while (i_node != m_active_nodes.end() && (*i_node)->line < node->line) { i_node++; } auto i_node_insert = i_node; // store insertion point // now check if there's another node with the same line number, // position, and fitness; if yes, drop the new node while (i_node != m_active_nodes.end() && (*i_node)->line == node->line) { if ((*i_node)->fitness_class == node->fitness_class && (*i_node)->position == node->position) { return; } i_node++; } m_active_nodes.insert(i_node_insert, node); } bool is_feasible_breakpoint(size_t i) { // we can break at position i if either i is a penalty less than infinity // or if it is a glue and the previous node is a box auto node = m_nodes[i]; if (node->type() == NodeType::penalty) { if (static_cast*>(node)->penalty() < Penalty::infinity) { return true; } } else if (i > 0 && node->type() == NodeType::glue) { if (m_nodes[i-1]->type() == NodeType::box) { return true; } } return false; } bool is_forced_break(size_t i) { // a penalty of -infinity is a forced break auto node = m_nodes[i]; if (node->type() == NodeType::penalty) { if (static_cast*>(node)->penalty() <= -1*Penalty::infinity) { return true; } } return false; } Length measure_width(size_t i1, size_t i2) { return m_sum_widths[i2] - m_sum_widths[i1]; } Length measure_stretch(size_t i1, size_t i2) { return m_sum_stretch[i2] - m_sum_stretch[i1]; } Length measure_shrink(size_t i1, size_t i2) { return m_sum_shrink[i2] - m_sum_shrink[i1]; } double compute_adjustment_ratio(size_t i1, size_t i2, size_t line) { Length len = measure_width(i1, i2); // if we're breaking at a penalty and it has a width, need to account for it // TODO: check if this is correct. Why don't we have to account for the widths of other box types? if (m_nodes[i2]->type() == NodeType::penalty) { len = len + m_nodes[i2]->width(); } // we obtain the available length of the current line // from the vector of line lengths or, if we have used them up, // from the last line length Length len_avail; if (line < m_line_lengths.size()) { len_avail = m_line_lengths[line]; } else { len_avail = m_line_lengths.back(); } double r = 0; // adjustment ratio if (len < len_avail) { // if length is smaller than available length, need to stretch Length stretch = measure_stretch(i1, i2); if (stretch > 0) { r = (len_avail - len)/stretch; } else { r = Glue::infinity; } } else if (len > len_avail) { // if length is larger than available length, need to shrink Length shrink = measure_shrink(i1, i2); if (shrink > 0) { r = (len_avail - len)/shrink; } else { r = -1*Glue::infinity; } } // r = 0 if len == len_avail return r; } int compute_fitness_class(double r) { // the fitness class of the line with adjustment ratio r (very tight, tight, loose, very loose) int fitness_class; if (r < -.5) fitness_class = 0; else if (r <= .5) fitness_class = 1; else if (r <= 1) fitness_class = 2; else fitness_class = 3; return fitness_class; } double compute_demerits(size_t i, size_t i_active, double r, int fitness_class, int fitness_class_active) { double demerits; if (m_p[i] >= 0) { demerits = pow(1 + 100 * pow(abs(r), 3) + m_p[i], 3); } else if (is_forced_break(i)) { demerits = pow(1 + 100 * pow(abs(r), 3), 2) - pow(m_p[i], 2); } else { demerits = pow(1 + 100 * pow(abs(r), 3), 2); } // adjust demeteris for flagged items demerits = demerits + (m_flagged_demerit * m_f[i] * m_f[i_active]); // add demerits for changes in fitness class if (abs(fitness_class - fitness_class_active) > 1) { demerits = demerits + m_fitness_demerit; } return demerits; } // to write unit tests that have access to private members friend class TestLineBreaker; public: LineBreaker(const BoxList& nodes, const vector &line_lengths, double tolerance = 1, double fitness_demerit = 100, double flagged_demerit = 100) : m_nodes(nodes), m_line_lengths(line_lengths), m_tolerance(tolerance), m_fitness_demerit(fitness_demerit), m_flagged_demerit(flagged_demerit) { // tolerance is \rho in the paper // fitness_demerit is \gamma in the paper size_t m = nodes.size(); // set up vectors with the five possible values (w, y, z, p, f) // for each node m_w.reserve(m); m_y.reserve(m); m_z.reserve(m); m_p.reserve(m); m_f.reserve(m); for (auto i_node = m_nodes.begin(); i_node != m_nodes.end(); i_node++) { m_w.push_back((*i_node)->width()); if ((*i_node)->type() == NodeType::glue) { m_y.push_back(static_cast*>(i_node->get())->stretch()); m_z.push_back(static_cast*>(i_node->get())->shrink()); m_p.push_back(0); m_f.push_back(false); } else if ((*i_node)->type() == NodeType::penalty) { m_y.push_back(0); m_z.push_back(0); m_p.push_back(static_cast*>(i_node->get())->penalty()); m_f.push_back(static_cast*>(i_node->get())->flagged()); } else { m_y.push_back(0); m_z.push_back(0); m_p.push_back(0); m_f.push_back(false); } } // pre-compute sums m_sum_widths.resize(m); m_sum_stretch.resize(m); m_sum_shrink.resize(m); Length widths_sum = 0; Length stretch_sum = 0; Length shrink_sum = 0; for (size_t i = 0; i < m; i++) { m_sum_widths[i] = widths_sum; m_sum_stretch[i] = stretch_sum; m_sum_shrink[i] = shrink_sum; widths_sum = widths_sum + m_w[i]; stretch_sum = stretch_sum + m_y[i]; shrink_sum = shrink_sum + m_z[i]; } } void compute_breaks(vector &final_breaks) { // we return the final breaks via this argument; // clear this vector to make sure we're starting from a clean slate final_breaks.clear(); size_t m = m_nodes.size(); // if there are no nodes we have no breaks if (m == 0) { return; } // set up list of active nodes, initialize with // break at beginning of text m_active_nodes.clear(); m_passive_nodes.clear(); auto ptr = unique_ptr(new Breakpoint(0, 0, 1, 0)); m_active_nodes.push_back(ptr); for (size_t b = 0; b < m; b++) { // we can only break at feasible breakpoints if (is_feasible_breakpoint(b)) { // main loop, p. 1159 auto i_active = m_active_nodes.begin(); do { array Dc = {Penalty::infinity, Penalty::infinity, Penalty::infinity, Penalty::infinity}; double D = Penalty::infinity; array Ac = {nullptr, nullptr, nullptr, nullptr}; // iterate over all currently active nodes and evaluate breaking // between there and b; we need to pay attention because we modify // the list as we iterate while (i_active != m_active_nodes.end()) { double r = compute_adjustment_ratio((*i_active)->position, b, (*i_active)->line); // remove active nodes when overfull line or forced break if (r < -1 || is_forced_break(b)) { m_passive_nodes.push_back(*i_active); i_active = m_active_nodes.erase(i_active); // this advances the iterator continue; // to avoid incrementing i_active twice } if (-1 <= r <= m_tolerance) { int fitness_class = compute_fitness_class(r); double d = compute_demerits(b, (*i_active)->position, r, fitness_class, (*i_active)->fitness_class); if (d < Dc[fitness_class]) { Dc[fitness_class] = d; Ac[fitness_class] = (i_active); if (d < D) D = d; // keep track of overall minimum demerits } } i_active++; // the paper lists a shortcut that we skip for now: // if line(a) >= j and j < j_0 then exit loop // should this be j > j_0? } if (D < Penalty::infinity) { // insert new active nodes for breaks from A_c to b for (int c = 0; c < 4; c++) { if (Dc[c] < D + m_fitness_demerit) { auto ptr = unique_ptr( new Breakpoint(b, Ac[c]->line + 1, c, Dc[c], Ac[c]) ); m_active_nodes.insert(i_active, ptr); } } } } while (i_active != m_active_nodes.end()); } if (m_active_nodes.size() == 0) { // TODO // do something drastic since there is no feasible solution } // find the active node with the lowest number of demerits auto i_active = m_active_nodes.begin(); double min_demerits = (*i_active)->demerits; auto i_min = i_active; while (true) { i_active++; if (i_active == m_active_nodes.end()) { break; } if ((*i_active)->demerits < min_demerits) { min_demerits = (*i_active)->demerits; i_min = i_active; } } // now build a list of break points going backwards from minimum // demerits node to beginning Breakpoint *p_node = *i_min; while (p_node != nullptr) { final_breaks.push_back(p_node->position); p_node = p_node->previous; } reverse(final_breaks.begin(), final_breaks.end()); } } }; */ #endif gridtext/src/grid.h0000644000176200001440000000446313510216744014003 0ustar liggesusers#ifndef GRID_H #define GRID_H #include using namespace Rcpp; #include using namespace std; #include "length.h" // This file defines a number of convenience functions that allow for the rapid construction // or manipulation of grid units or grobs. Each could be replaced by a simple R call to a // corresponding grid function (e.g., unit_pt(x) is equivalent to unit(x, "pt")), but in general // the C++ version here is much faster, in particular because it skips extensive input validation. // replacement for unit(x, "pt") // [[Rcpp::export]] NumericVector unit_pt(NumericVector x); // Overloaded version for Length NumericVector unit_pt(Length x); // replacement for gpar() with no arguments // [[Rcpp::export]] List gpar_empty(); // replacement for textGrop(label, x_pt, y_pt, gp = gpar(), hjust = 0, vjust = 0, default.units = "pt", name = NULL) // [[Rcpp::export]] List text_grob(CharacterVector label, NumericVector x_pt = 0, NumericVector y_pt = 0, RObject gp = R_NilValue, RObject name = R_NilValue); // replacement for rasterGrop(image, x_pt, y_pt, width_pt, height_pt, gp = gpar(), hjust = 0, vjust = 0, default.units = "pt", interpolate = TRUE, name = NULL) // [[Rcpp::export]] List raster_grob(RObject image, NumericVector x_pt = 0, NumericVector y_pt = 0, NumericVector width_pt = 0, NumericVector height_pt = 0, LogicalVector interpolate = true, RObject gp = R_NilValue, RObject name = R_NilValue); // replacement for rectGrop(x_pt, y_pt, width_pt, height_pt, gp = gpar(), hjust = 0, vjust = 0, default.units = "pt", name = NULL) // [[Rcpp::export]] List rect_grob(NumericVector x_pt = 0, NumericVector y_pt = 0, NumericVector width_pt = 0, NumericVector height_pt = 0, RObject gp = R_NilValue, RObject name = R_NilValue); // replacement for roundrectGrop(x_pt, y_pt, width_pt, height_pt, r = unit(r_pt, "pt), gp = gpar(), just = c(0, 0), default.units = "pt", name = NULL) // [[Rcpp::export]] List roundrect_grob(NumericVector x_pt = 0, NumericVector y_pt = 0, NumericVector width_pt = 0, NumericVector height_pt = 0, NumericVector r_pt = 5, RObject gp = R_NilValue, RObject name = R_NilValue); // replacement for editGrob(grob, x = x, y = y) // [[Rcpp::export]] RObject set_grob_coords(RObject grob, NumericVector x, NumericVector y); #endif gridtext/src/par-box.h0000644000176200001440000001226313523051702014416 0ustar liggesusers#ifndef PAR_BOX_H #define PAR_BOX_H #include using namespace Rcpp; #include #include "grid.h" #include "layout.h" //#include "glue.h" //#include "penalty.h" #include "line-breaker.h" /* The ParBox class takes a list of boxes and lays them out * horizontally, breaking lines if necessary. The reference point * is the left end point of the baseline of the last line. */ template class ParBox : public Box { private: BoxList m_nodes; Length m_vspacing; Length m_width; Length m_ascent; Length m_descent; Length m_voff; SizePolicy m_width_policy; double m_hjust; // horizontal adjustment; can be used to override text adjustment bool m_use_hjust; // should text adjustment be overridden or not? // vertical shift if paragraph contains more than one line; is used to make sure the // bottom line in the box is used as the box baseline (all lines above are folded // into the ascent) Length m_multiline_shift; // calculated left baseline corner of the box after layouting Length m_x, m_y; public: ParBox(const BoxList& nodes, Length vspacing, SizePolicy width_policy = SizePolicy::native, double hjust = 0, bool use_hjust = false) : m_nodes(nodes), m_vspacing(vspacing), m_width(0), m_ascent(0), m_descent(0), m_voff(0), m_width_policy(width_policy), m_hjust(hjust), m_use_hjust(use_hjust), m_multiline_shift(0), m_x(0), m_y(0) { } ~ParBox() {}; Length width() { return m_width; } Length ascent() { return m_ascent; } Length descent() { return m_descent; } Length voff() { return m_voff; } void calc_layout(Length width_hint, Length height_hint) { // first make sure all child nodes are in a defined state // we propagate width and height hints to all child nodes, // in case they are useful there for (auto i_node = m_nodes.begin(); i_node != m_nodes.end(); i_node++) { (*i_node)->calc_layout(width_hint, height_hint); } // choose breaking parameters based on size policy bool word_wrap = true; if (m_width_policy == SizePolicy::native) { // for native policy, we don't wrap words and we allow lines to be arbitrarily long word_wrap = false; width_hint = Glue::infinity; } // calculate line breaks vector line_lengths = {width_hint}; LineBreaker lb(m_nodes, line_lengths, word_wrap); vector line_breaks; lb.compute_line_breaks(line_breaks); // now get the true line length for native size policy, // by finding the longest line if (m_width_policy == SizePolicy::native) { width_hint = 0; for (auto i_line = line_breaks.begin(); i_line != line_breaks.end(); i_line++) { if (width_hint < i_line->width) { width_hint = i_line->width; } } } // now place all nodes according to line breaks Length x_off = 0, y_off = 0; // x and y offset as we layout int lines = 0; Length first_ascent = 0; // ascent of the first line Length descent = 0; for (auto i_line = line_breaks.begin(); i_line != line_breaks.end(); i_line++) { // reset x_off for new line, potentially overriding alignment if (m_use_hjust) { x_off = m_hjust*(width_hint - i_line->width); } else { x_off = 0; } // we first get the ascent of each box in the line, to make sure there is // vertical space if some boxes are very tall Length ascent = 0; for (size_t i = i_line->start; i != i_line->end; i++) { auto node = m_nodes[i]; Length ascent_new = node->ascent() + node->voff(); if (ascent_new > ascent) { ascent = ascent_new; } } if (lines == 0) { // are we rendering the first line? // yes, record ascent for first line first_ascent = ascent; } else { // no, adjust y_offset as needed if (ascent + descent > m_vspacing) { y_off = y_off - (ascent + descent); } else { y_off = y_off - m_vspacing; } } // reset descent for new line descent = 0; // now loop over all boxes in each line and place for (size_t i = i_line->start; i != i_line->end; i++) { auto node = m_nodes[i]; node->place(x_off, y_off); x_off += node->width(); // record new descent Length descent_new = node->descent() - node->voff(); if (descent_new > descent) { descent = descent_new; } } // advance line lines += 1; } if (lines > 0) { // at least one line? m_multiline_shift = -1 * y_off; // multi-line boxes need to be shifted upwards m_ascent = first_ascent - y_off; m_descent = descent; m_width = width_hint; } else { m_multiline_shift = 0; m_ascent = 0; m_descent = 0; m_width = width_hint; } } void place(Length x, Length y) { m_x = x; m_y = y; } void render(Renderer &r, Length xref, Length yref) { // render all grobs in the list for (auto i_node = m_nodes.begin(); i_node != m_nodes.end(); i_node++) { (*i_node)->render(r, xref + m_x, yref + m_voff + m_y + m_multiline_shift); } } }; #endif gridtext/src/layout.h0000644000176200001440000000524513510725352014372 0ustar liggesusers#ifndef LAYOUT_H #define LAYOUT_H #include using namespace Rcpp; #include #include using namespace std; #include "length.h" enum class NodeType { none, box, glue, penalty }; enum class SizePolicy { fixed, // box size is fixed upon construction native, // box determines its own ideal size expand, // box expands as much as possible relative /* box takes up a set proportion of the size hint * provided to calc_layout(); in this case * Length units are interpreted as percent, i.e., * a Length of 100 means full size */ }; // base class for a generic node in the // layout tree template class BoxNode { public: BoxNode() {} virtual ~BoxNode() {} // returns the node type (box, glue, penalty) virtual NodeType type() = 0; // width of the box virtual Length width() = 0; // ascent of the box (height measured from baseline) virtual Length ascent() = 0; // descent of the box (height below the baseline) virtual Length descent() = 0; // vertical offset (vertical shift of baseline) virtual Length height() { return ascent() + descent(); } virtual Length voff() = 0; // calculate the internal layout of the box // in the general case, we may provide the box with a width and // a height to render into, though boxes may ignore these virtual void calc_layout(Length width_hint = 0, Length height_hint = 0) = 0; // place box in internal coordinates used in enclosing box virtual void place(Length x, Length y) = 0; // render into absolute coordinates, using the reference coordinates // from the enclosing box virtual void render(Renderer &r, Length xref, Length yref) = 0; }; template class Box : public BoxNode { Length m_width, m_stretch, m_shrink; public: Box() {} ~Box() {} NodeType type() {return NodeType::box;} }; // box list (vector of pointers to boxes) template using BoxPtr = XPtr>; template using BoxList = vector>; // struct that holds width, ascent, etc. data for text labels struct TextDetails { Length width; // width of the label Length ascent; // ascent from baseline Length descent; // descent below baseline Length space; // width of a space TextDetails(Length w = 0, Length a = 0, Length d = 0, Length s = 0) : width(w), ascent(a), descent(d), space(s) {} }; // struct that holds margin or padding info, in the form top, right, bottom, left struct Margin { Length top; Length right; Length bottom; Length left; Margin(Length t = 0, Length r = 0, Length b = 0, Length l = 0) : top(t), right(r), bottom(b), left(l) {} }; #endif gridtext/src/gridtext_types.h0000644000176200001440000000057713506705473016145 0ustar liggesusers#ifndef GRIDTEXT_TYPES_H #define GRIDTEXT_TYPES_H /* By naming this file _types.h, we can make sure * it is automatically included in the autogenerated RcppExports.cpp. * * The file includes exactly those classes that are exported via * an XPtr. It is not used anywhere else in the C++ part of the code. */ #include "layout.h" #include "grid-renderer.h" #endif gridtext/src/penalty.h0000644000176200001440000000245513521342614014527 0ustar liggesusers#ifndef PENALTY_H #define PENALTY_H #include using namespace Rcpp; #include "layout.h" template class Penalty : public BoxNode { private: int m_penalty; Length m_width; bool m_flagged; public: static constexpr int infinity = 10000; // maximum penalty Penalty(int penalty = 0, Length width = 0, bool flagged = false) : m_penalty(penalty), m_width(width), m_flagged(flagged) {} virtual ~Penalty() {} NodeType type() {return NodeType::penalty;} Length width() {return m_width;} Length ascent() {return 0;} Length descent() {return 0;} Length voff() {return 0;} void calc_layout(Length, Length) {} void place(Length, Length) {} void render(Renderer &, Length, Length) {} /* The remaining functions are not virtual, * don't override. */ int penalty() {return m_penalty;} bool flagged() {return m_flagged;} }; // Penalty that causes a forced break template class ForcedBreakPenalty : public Penalty { public: ForcedBreakPenalty() : Penalty(-1*Penalty::infinity) {} }; // Penalty that prevents a break at this position template class NeverBreakPenalty : public Penalty { public: NeverBreakPenalty() : Penalty(Penalty::infinity) {} }; #endif gridtext/src/text-box.h0000644000176200001440000000306113524315550014621 0ustar liggesusers#ifndef TEXT_BOX_H #define TEXT_BOX_H #include using namespace Rcpp; #include "layout.h" // A box holding a single text label template class TextBox : public Box { private: CharacterVector m_label; typename Renderer::GraphicsContext m_gp; Length m_width; Length m_ascent; Length m_descent; Length m_voff; // position of the box in enclosing box, modulo vertical offset (voff), // which gets added to m_y; // the box reference point is the leftmost point of the baseline. Length m_x, m_y; public: TextBox(const CharacterVector &label, const typename Renderer::GraphicsContext &gp, Length voff = 0) : m_label(label), m_gp(gp), m_width(0), m_ascent(0), m_descent(0), m_voff(voff), m_x(0), m_y(0) {} ~TextBox() {} Length width() { return m_width; } Length ascent() { return m_ascent; } Length descent() { return m_descent; } Length voff() { return m_voff; } // width and height are only defined once `calc_layout()` has been called void calc_layout(Length, Length) { TextDetails td = Renderer::text_details(m_label, m_gp); m_width = td.width; m_ascent = td.ascent; m_descent = td.descent; } // place box in internal coordinates used in enclosing box void place(Length x, Length y) { m_x = x; m_y = y; } // render into absolute coordinates, using the reference coordinates // from the enclosing box void render(Renderer &r, Length xref, Length yref) { Length x = m_x + xref; Length y = m_y + m_voff + yref; r.text(m_label, x, y, m_gp); } }; #endif gridtext/src/glue.h0000644000176200001440000000427013521336522014005 0ustar liggesusers#ifndef GLUE_H #define GLUE_H #include using namespace Rcpp; #include "layout.h" template class Glue : public BoxNode { protected: Length m_width, m_stretch, m_shrink; double m_r; // adjustment ratio public: static constexpr double infinity = 1e9; // maximum adjustment ratio Glue(Length width = 0, Length stretch = 0, Length shrink = 0) : m_width(width), m_stretch(stretch), m_shrink(shrink), m_r(0) {} virtual ~Glue() {} NodeType type() {return NodeType::glue;} Length width() {return compute_width(m_r);} Length ascent() {return 0;} Length descent() {return 0;} Length voff() {return 0;} void calc_layout(Length, Length) {} void place(Length, Length) {} void render(Renderer &, Length, Length) {} /* The remaining functions are not virtual, * don't override. */ Length default_width() {return m_width;} Length stretch() {return m_stretch;} Length shrink() {return m_shrink;} void set_r(double r) {m_r = r;} // calculate the width of the glue for given adjustment ratio Length compute_width(double r) { if (r < 0) { return m_width + r*m_shrink; } else { return m_width + r*m_stretch; } } }; // Glue corresponding to a regular space in text template class RegularSpaceGlue : public Glue { private: typename Renderer::GraphicsContext m_gp; double m_stretch_ratio, m_shrink_ratio; // used to convert width of space character into stretch and shrink // pull protected members from superclass explicitly into scope using Glue::m_width; using Glue::m_stretch; using Glue::m_shrink; public: RegularSpaceGlue(const typename Renderer::GraphicsContext &gp, double stretch_ratio = 0.5, double shrink_ratio = 0.333333) : m_gp(gp), m_stretch_ratio(stretch_ratio), m_shrink_ratio(shrink_ratio) {} ~RegularSpaceGlue() {} // width, stretch, and shrink are only defined once `calc_layout()` has been called void calc_layout(Length, Length) { TextDetails td = Renderer::text_details(" ", m_gp); m_width = td.space; m_stretch = m_width * m_stretch_ratio; m_shrink = m_width * m_shrink_ratio; } }; #endif gridtext/src/bl-r-bindings.cpp0000644000176200001440000001742713524316013016037 0ustar liggesusers#include using namespace Rcpp; #include "layout.h" #include "null-box.h" #include "par-box.h" #include "raster-box.h" #include "rect-box.h" #include "text-box.h" #include "vbox.h" #include "grid-renderer.h" /* Various helper functions (not exported) */ Margin convert_margin(NumericVector margin) { if (margin.size() != 4) { stop("Margin must have exactly four elements."); } return Margin(margin[0], margin[1], margin[2], margin[3]); } SizePolicy convert_size_policy(String size_policy) { // we identify the size policy simply by its first letter switch (size_policy.get_cstring()[0]) { case 'n': return SizePolicy::native; case 'e': return SizePolicy::expand; case 'r': return SizePolicy::relative; case 'f': default: return SizePolicy::fixed; } } BoxList make_node_list(const List &nodes) { BoxList nlist; nlist.reserve(nodes.size()); for (auto i_node = nodes.begin(); i_node != nodes.end(); i_node++) { RObject obj(static_cast(*i_node)); if (!obj.inherits("bl_node")) { stop("All list elements must be of type 'bl_node'."); } BoxPtr p(obj); nlist.push_back(p); } return nlist; } /* Exported R bindings */ /* * Constructors for boxes */ // [[Rcpp::export]] BoxPtr bl_make_null_box(double width_pt = 0, double height_pt = 0) { BoxPtr p(new NullBox(width_pt, height_pt)); StringVector cl = {"bl_null_box", "bl_box", "bl_node"}; p.attr("class") = cl; return p; } // [[Rcpp::export]] BoxPtr bl_make_par_box(const List &node_list, double vspacing_pt, String width_policy = "native", RObject hjust = R_NilValue) { SizePolicy w_policy = convert_size_policy(width_policy); double hjust_val = 0; double use_hjust = false; if (!hjust.isNULL()) { NumericVector hj = as(hjust); if (hj.size() > 0 && !NumericVector::is_na(hj[0])) { hjust_val = hj[0]; use_hjust = true; } } BoxList nodes(make_node_list(node_list)); BoxPtr p(new ParBox(nodes, vspacing_pt, w_policy, hjust_val, use_hjust)); StringVector cl = {"bl_par_box", "bl_box", "bl_node"}; p.attr("class") = cl; return p; } // [[Rcpp::export]] BoxPtr bl_make_rect_box(RObject content, double width_pt, double height_pt, NumericVector margin, NumericVector padding, List gp, double content_hjust = 0, double content_vjust = 1, String width_policy = "fixed", String height_policy = "fixed", double r = 0) { if (!content.isNULL() && !content.inherits("bl_box")) { stop("Contents must be of type 'bl_box'."); } Margin marg = convert_margin(margin); Margin pad = convert_margin(padding); SizePolicy w_policy = convert_size_policy(width_policy); SizePolicy h_policy = convert_size_policy(height_policy); StringVector cl = {"bl_rect_box", "bl_box", "bl_node"}; if (content.isNULL()) { // R doesn't like null pointers, so we have to create // a null box instead BoxPtr nb(new NullBox(0, 0)); BoxPtr p(new RectBox( nb, width_pt, height_pt, marg, pad, gp, content_hjust, content_vjust, w_policy, h_policy, r )); p.attr("class") = cl; return p; } else { BoxPtr p(new RectBox( as>(content), width_pt, height_pt, marg, pad, gp, content_hjust, content_vjust, w_policy, h_policy, r )); p.attr("class") = cl; return p; } } // [[Rcpp::export]] BoxPtr bl_make_text_box(const CharacterVector &label, List gp, double voff_pt = 0) { if (label.size() != 1) { stop("TextBox requires a label vector of length 1."); } BoxPtr p(new TextBox(label, gp, voff_pt)); StringVector cl = {"bl_text_box", "bl_box", "bl_node"}; p.attr("class") = cl; return p; } // [[Rcpp::export]] BoxPtr bl_make_raster_box(RObject image, double width_pt = 0, double height_pt = 0, String width_policy = "native", String height_policy = "native", bool respect_aspect = true, bool interpolate = true, double dpi = 150, List gp = R_NilValue) { SizePolicy w_policy = convert_size_policy(width_policy); SizePolicy h_policy = convert_size_policy(height_policy); BoxPtr p(new RasterBox( image, width_pt, height_pt, gp, w_policy, h_policy, respect_aspect, interpolate, dpi)); StringVector cl = {"bl_raster_box", "bl_box", "bl_node"}; p.attr("class") = cl; return p; } // [[Rcpp::export]] BoxPtr bl_make_vbox(const List &node_list, double width_pt = 0, double hjust = 0, double vjust = 1, String width_policy = "native") { SizePolicy w_policy = convert_size_policy(width_policy); BoxList nodes(make_node_list(node_list)); BoxPtr p(new VBox(nodes, width_pt, hjust, vjust, w_policy)); StringVector cl = {"bl_vbox", "bl_box", "bl_node"}; p.attr("class") = cl; return p; } /* * Constructors for glue */ // [[Rcpp::export]] BoxPtr bl_make_regular_space_glue(List gp, double stretch_ratio = 0.5, double shrink_ratio = 0.333333) { BoxPtr p(new RegularSpaceGlue(gp, stretch_ratio, shrink_ratio)); StringVector cl = {"bl_regular_space_glue", "bl_glue", "bl_node"}; p.attr("class") = cl; return p; } /* * Constructors for penalties */ // [[Rcpp::export]] BoxPtr bl_make_forced_break_penalty() { BoxPtr p(new ForcedBreakPenalty()); StringVector cl = {"bl_forced_break_penalty", "bl_penalty", "bl_node"}; p.attr("class") = cl; return p; } // [[Rcpp::export]] BoxPtr bl_make_never_break_penalty() { BoxPtr p(new NeverBreakPenalty()); StringVector cl = {"bl_never_break_penalty", "bl_penalty", "bl_node"}; p.attr("class") = cl; return p; } /* * Call member functions */ // [[Rcpp::export]] double bl_box_width(BoxPtr node) { if (!node.inherits("bl_node")) { stop("Node must be of type 'bl_node'."); } return node->width(); } // [[Rcpp::export]] double bl_box_height(BoxPtr node) { if (!node.inherits("bl_node")) { stop("Node must be of type 'bl_node'."); } return node->height(); } // [[Rcpp::export]] double bl_box_ascent(BoxPtr node) { if (!node.inherits("bl_node")) { stop("Node must be of type 'bl_node'."); } return node->ascent(); } // [[Rcpp::export]] double bl_box_descent(BoxPtr node) { if (!node.inherits("bl_node")) { stop("Node must be of type 'bl_node'."); } return node->descent(); } // [[Rcpp::export]] double bl_box_voff(BoxPtr node) { if (!node.inherits("bl_node")) { stop("Node must be of type 'bl_node'."); } return node->voff(); } // [[Rcpp::export]] void bl_calc_layout(BoxPtr node, double width_pt = 0, double height_pt = 0) { if (!node.inherits("bl_node")) { stop("Node must be of type 'bl_node'."); } node->calc_layout(width_pt, height_pt); } // [[Rcpp::export]] void bl_place(BoxPtr node, double x_pt, double y_pt) { if (!node.inherits("bl_node")) { stop("Node must be of type 'bl_node'."); } node->place(x_pt, y_pt); } // [[Rcpp::export]] RObject bl_render(BoxPtr node, double x_pt = 0, double y_pt = 0) { if (!node.inherits("bl_node")) { stop("Node must be of type 'bl_node'."); } GridRenderer gr; node->render(gr, x_pt, y_pt); return gr.collect_grobs(); } gridtext/src/grid-renderer.h0000644000176200001440000000766113524315670015615 0ustar liggesusers#ifndef GRID_RENDERER_H #define GRID_RENDERER_H #include using namespace Rcpp; #include #include "grid.h" #include "length.h" #include "layout.h" class GridRenderer { public: // GridRenderer stores its graphics context in a grid gpar() list typedef List GraphicsContext; private: vector m_grobs; RObject gpar_lookup(List gp, const char* element) { if (!gp.containsElementNamed(element)) { return R_NilValue; } else { return gp[element]; } } public: GridRenderer() { } static TextDetails text_details(const CharacterVector &label, GraphicsContext gp) { // call R function to look up text info Environment env = Environment::namespace_env("gridtext"); Function td = env["text_details"]; List info = td(label, gp); RObject width_pt = info["width_pt"]; RObject ascent_pt = info["ascent_pt"]; RObject descent_pt = info["descent_pt"]; RObject space_pt = info["space_pt"]; return TextDetails( NumericVector(width_pt)[0], NumericVector(ascent_pt)[0], NumericVector(descent_pt)[0], NumericVector(space_pt)[0] ); } void text(const CharacterVector &label, Length x, Length y, const GraphicsContext &gp) { m_grobs.push_back(text_grob(label, NumericVector(1, x), NumericVector(1, y), gp)); } void raster(RObject image, Length x, Length y, Length width, Length height, bool interpolate = true, const GraphicsContext &gp = R_NilValue) { if (!image.isNULL()) { m_grobs.push_back( raster_grob( image, NumericVector(1, x), NumericVector(1, y), NumericVector(1, width), NumericVector(1, height), LogicalVector(1, interpolate, gp) ) ); } } void rect(Length x, Length y, Length width, Length height, const GraphicsContext &gp, Length r = 0) { // skip drawing if nothing would show anyways // default assumption is we don't have a fill color but we do have line color and type bool have_fill_col = false; bool have_line_col = true; bool have_line_type = true; RObject fill_obj = gpar_lookup(gp, "fill"); if (!fill_obj.isNULL()) { CharacterVector fill(fill_obj); if (fill.size() > 0 && !CharacterVector::is_na(fill[0])) { have_fill_col = true; } } // if we have a fill color, further checks don't matter if (!have_fill_col) { RObject color = gpar_lookup(gp, "col"); if (!color.isNULL()) { CharacterVector col(color); if (col.size() == 0 || CharacterVector::is_na(col[0])) { have_line_col = false; } } } // if we don't have a fill color but do have a line color, // need to check line type if (!have_fill_col && have_line_col) { RObject linetype = gpar_lookup(gp, "lty"); if (!linetype.isNULL()) { NumericVector lty(linetype); if (lty.size() == 0 || lty[0] == 0) { have_line_type = false; } } } if (!have_fill_col && (!have_line_col || !have_line_type)) { return; } // now that we know we should draw, go ahead NumericVector xv(1, x), yv(1, y), widthv(1, width), heightv(1, height); // draw simple rect grob or rounded rect grob depending on provided radius if (r < 0.01) { m_grobs.push_back(rect_grob(xv, yv, widthv, heightv, gp)); } else { NumericVector rv(1, r); m_grobs.push_back(roundrect_grob(xv, yv, widthv, heightv, rv, gp)); } } List collect_grobs() { // turn vector of grobs into list; doing it this way avoids // List.push_back() which is slow. List out(m_grobs.size()); size_t i = 0; for (auto i_grob = m_grobs.begin(); i_grob != m_grobs.end(); i_grob++) { out[i] = *i_grob; i++; } // clear internal grobs list; the renderer is reset with each collect_grobs() call m_grobs.clear(); // turn list into gList to keep grid happy out.attr("class") = "gList"; return out; } }; #endif gridtext/src/length.h0000644000176200001440000000027113505751366014340 0ustar liggesusers#ifndef LENGTH_H #define LENGTH_H // for now, we just typedef Length as a double. // in the future, something more elaborate may be // needed, as in TeX typedef double Length; #endif gridtext/src/RcppExports.cpp0000644000176200001440000005337313760021121015674 0ustar liggesusers// Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #include "gridtext_types.h" #include using namespace Rcpp; // bl_make_null_box BoxPtr bl_make_null_box(double width_pt, double height_pt); RcppExport SEXP _gridtext_bl_make_null_box(SEXP width_ptSEXP, SEXP height_ptSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< double >::type width_pt(width_ptSEXP); Rcpp::traits::input_parameter< double >::type height_pt(height_ptSEXP); rcpp_result_gen = Rcpp::wrap(bl_make_null_box(width_pt, height_pt)); return rcpp_result_gen; END_RCPP } // bl_make_par_box BoxPtr bl_make_par_box(const List& node_list, double vspacing_pt, String width_policy, RObject hjust); RcppExport SEXP _gridtext_bl_make_par_box(SEXP node_listSEXP, SEXP vspacing_ptSEXP, SEXP width_policySEXP, SEXP hjustSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< const List& >::type node_list(node_listSEXP); Rcpp::traits::input_parameter< double >::type vspacing_pt(vspacing_ptSEXP); Rcpp::traits::input_parameter< String >::type width_policy(width_policySEXP); Rcpp::traits::input_parameter< RObject >::type hjust(hjustSEXP); rcpp_result_gen = Rcpp::wrap(bl_make_par_box(node_list, vspacing_pt, width_policy, hjust)); return rcpp_result_gen; END_RCPP } // bl_make_rect_box BoxPtr bl_make_rect_box(RObject content, double width_pt, double height_pt, NumericVector margin, NumericVector padding, List gp, double content_hjust, double content_vjust, String width_policy, String height_policy, double r); RcppExport SEXP _gridtext_bl_make_rect_box(SEXP contentSEXP, SEXP width_ptSEXP, SEXP height_ptSEXP, SEXP marginSEXP, SEXP paddingSEXP, SEXP gpSEXP, SEXP content_hjustSEXP, SEXP content_vjustSEXP, SEXP width_policySEXP, SEXP height_policySEXP, SEXP rSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< RObject >::type content(contentSEXP); Rcpp::traits::input_parameter< double >::type width_pt(width_ptSEXP); Rcpp::traits::input_parameter< double >::type height_pt(height_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type margin(marginSEXP); Rcpp::traits::input_parameter< NumericVector >::type padding(paddingSEXP); Rcpp::traits::input_parameter< List >::type gp(gpSEXP); Rcpp::traits::input_parameter< double >::type content_hjust(content_hjustSEXP); Rcpp::traits::input_parameter< double >::type content_vjust(content_vjustSEXP); Rcpp::traits::input_parameter< String >::type width_policy(width_policySEXP); Rcpp::traits::input_parameter< String >::type height_policy(height_policySEXP); Rcpp::traits::input_parameter< double >::type r(rSEXP); rcpp_result_gen = Rcpp::wrap(bl_make_rect_box(content, width_pt, height_pt, margin, padding, gp, content_hjust, content_vjust, width_policy, height_policy, r)); return rcpp_result_gen; END_RCPP } // bl_make_text_box BoxPtr bl_make_text_box(const CharacterVector& label, List gp, double voff_pt); RcppExport SEXP _gridtext_bl_make_text_box(SEXP labelSEXP, SEXP gpSEXP, SEXP voff_ptSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< const CharacterVector& >::type label(labelSEXP); Rcpp::traits::input_parameter< List >::type gp(gpSEXP); Rcpp::traits::input_parameter< double >::type voff_pt(voff_ptSEXP); rcpp_result_gen = Rcpp::wrap(bl_make_text_box(label, gp, voff_pt)); return rcpp_result_gen; END_RCPP } // bl_make_raster_box BoxPtr bl_make_raster_box(RObject image, double width_pt, double height_pt, String width_policy, String height_policy, bool respect_aspect, bool interpolate, double dpi, List gp); RcppExport SEXP _gridtext_bl_make_raster_box(SEXP imageSEXP, SEXP width_ptSEXP, SEXP height_ptSEXP, SEXP width_policySEXP, SEXP height_policySEXP, SEXP respect_aspectSEXP, SEXP interpolateSEXP, SEXP dpiSEXP, SEXP gpSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< RObject >::type image(imageSEXP); Rcpp::traits::input_parameter< double >::type width_pt(width_ptSEXP); Rcpp::traits::input_parameter< double >::type height_pt(height_ptSEXP); Rcpp::traits::input_parameter< String >::type width_policy(width_policySEXP); Rcpp::traits::input_parameter< String >::type height_policy(height_policySEXP); Rcpp::traits::input_parameter< bool >::type respect_aspect(respect_aspectSEXP); Rcpp::traits::input_parameter< bool >::type interpolate(interpolateSEXP); Rcpp::traits::input_parameter< double >::type dpi(dpiSEXP); Rcpp::traits::input_parameter< List >::type gp(gpSEXP); rcpp_result_gen = Rcpp::wrap(bl_make_raster_box(image, width_pt, height_pt, width_policy, height_policy, respect_aspect, interpolate, dpi, gp)); return rcpp_result_gen; END_RCPP } // bl_make_vbox BoxPtr bl_make_vbox(const List& node_list, double width_pt, double hjust, double vjust, String width_policy); RcppExport SEXP _gridtext_bl_make_vbox(SEXP node_listSEXP, SEXP width_ptSEXP, SEXP hjustSEXP, SEXP vjustSEXP, SEXP width_policySEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< const List& >::type node_list(node_listSEXP); Rcpp::traits::input_parameter< double >::type width_pt(width_ptSEXP); Rcpp::traits::input_parameter< double >::type hjust(hjustSEXP); Rcpp::traits::input_parameter< double >::type vjust(vjustSEXP); Rcpp::traits::input_parameter< String >::type width_policy(width_policySEXP); rcpp_result_gen = Rcpp::wrap(bl_make_vbox(node_list, width_pt, hjust, vjust, width_policy)); return rcpp_result_gen; END_RCPP } // bl_make_regular_space_glue BoxPtr bl_make_regular_space_glue(List gp, double stretch_ratio, double shrink_ratio); RcppExport SEXP _gridtext_bl_make_regular_space_glue(SEXP gpSEXP, SEXP stretch_ratioSEXP, SEXP shrink_ratioSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type gp(gpSEXP); Rcpp::traits::input_parameter< double >::type stretch_ratio(stretch_ratioSEXP); Rcpp::traits::input_parameter< double >::type shrink_ratio(shrink_ratioSEXP); rcpp_result_gen = Rcpp::wrap(bl_make_regular_space_glue(gp, stretch_ratio, shrink_ratio)); return rcpp_result_gen; END_RCPP } // bl_make_forced_break_penalty BoxPtr bl_make_forced_break_penalty(); RcppExport SEXP _gridtext_bl_make_forced_break_penalty() { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; rcpp_result_gen = Rcpp::wrap(bl_make_forced_break_penalty()); return rcpp_result_gen; END_RCPP } // bl_make_never_break_penalty BoxPtr bl_make_never_break_penalty(); RcppExport SEXP _gridtext_bl_make_never_break_penalty() { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; rcpp_result_gen = Rcpp::wrap(bl_make_never_break_penalty()); return rcpp_result_gen; END_RCPP } // bl_box_width double bl_box_width(BoxPtr node); RcppExport SEXP _gridtext_bl_box_width(SEXP nodeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< BoxPtr >::type node(nodeSEXP); rcpp_result_gen = Rcpp::wrap(bl_box_width(node)); return rcpp_result_gen; END_RCPP } // bl_box_height double bl_box_height(BoxPtr node); RcppExport SEXP _gridtext_bl_box_height(SEXP nodeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< BoxPtr >::type node(nodeSEXP); rcpp_result_gen = Rcpp::wrap(bl_box_height(node)); return rcpp_result_gen; END_RCPP } // bl_box_ascent double bl_box_ascent(BoxPtr node); RcppExport SEXP _gridtext_bl_box_ascent(SEXP nodeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< BoxPtr >::type node(nodeSEXP); rcpp_result_gen = Rcpp::wrap(bl_box_ascent(node)); return rcpp_result_gen; END_RCPP } // bl_box_descent double bl_box_descent(BoxPtr node); RcppExport SEXP _gridtext_bl_box_descent(SEXP nodeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< BoxPtr >::type node(nodeSEXP); rcpp_result_gen = Rcpp::wrap(bl_box_descent(node)); return rcpp_result_gen; END_RCPP } // bl_box_voff double bl_box_voff(BoxPtr node); RcppExport SEXP _gridtext_bl_box_voff(SEXP nodeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< BoxPtr >::type node(nodeSEXP); rcpp_result_gen = Rcpp::wrap(bl_box_voff(node)); return rcpp_result_gen; END_RCPP } // bl_calc_layout void bl_calc_layout(BoxPtr node, double width_pt, double height_pt); RcppExport SEXP _gridtext_bl_calc_layout(SEXP nodeSEXP, SEXP width_ptSEXP, SEXP height_ptSEXP) { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< BoxPtr >::type node(nodeSEXP); Rcpp::traits::input_parameter< double >::type width_pt(width_ptSEXP); Rcpp::traits::input_parameter< double >::type height_pt(height_ptSEXP); bl_calc_layout(node, width_pt, height_pt); return R_NilValue; END_RCPP } // bl_place void bl_place(BoxPtr node, double x_pt, double y_pt); RcppExport SEXP _gridtext_bl_place(SEXP nodeSEXP, SEXP x_ptSEXP, SEXP y_ptSEXP) { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< BoxPtr >::type node(nodeSEXP); Rcpp::traits::input_parameter< double >::type x_pt(x_ptSEXP); Rcpp::traits::input_parameter< double >::type y_pt(y_ptSEXP); bl_place(node, x_pt, y_pt); return R_NilValue; END_RCPP } // bl_render RObject bl_render(BoxPtr node, double x_pt, double y_pt); RcppExport SEXP _gridtext_bl_render(SEXP nodeSEXP, SEXP x_ptSEXP, SEXP y_ptSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< BoxPtr >::type node(nodeSEXP); Rcpp::traits::input_parameter< double >::type x_pt(x_ptSEXP); Rcpp::traits::input_parameter< double >::type y_pt(y_ptSEXP); rcpp_result_gen = Rcpp::wrap(bl_render(node, x_pt, y_pt)); return rcpp_result_gen; END_RCPP } // grid_renderer XPtr grid_renderer(); RcppExport SEXP _gridtext_grid_renderer() { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; rcpp_result_gen = Rcpp::wrap(grid_renderer()); return rcpp_result_gen; END_RCPP } // grid_renderer_text void grid_renderer_text(XPtr gr, const CharacterVector& label, Length x, Length y, List gp); RcppExport SEXP _gridtext_grid_renderer_text(SEXP grSEXP, SEXP labelSEXP, SEXP xSEXP, SEXP ySEXP, SEXP gpSEXP) { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< XPtr >::type gr(grSEXP); Rcpp::traits::input_parameter< const CharacterVector& >::type label(labelSEXP); Rcpp::traits::input_parameter< Length >::type x(xSEXP); Rcpp::traits::input_parameter< Length >::type y(ySEXP); Rcpp::traits::input_parameter< List >::type gp(gpSEXP); grid_renderer_text(gr, label, x, y, gp); return R_NilValue; END_RCPP } // grid_renderer_text_details List grid_renderer_text_details(const CharacterVector& label, List gp); RcppExport SEXP _gridtext_grid_renderer_text_details(SEXP labelSEXP, SEXP gpSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< const CharacterVector& >::type label(labelSEXP); Rcpp::traits::input_parameter< List >::type gp(gpSEXP); rcpp_result_gen = Rcpp::wrap(grid_renderer_text_details(label, gp)); return rcpp_result_gen; END_RCPP } // grid_renderer_raster void grid_renderer_raster(XPtr gr, RObject image, Length x, Length y, Length width, Length height, bool interpolate); RcppExport SEXP _gridtext_grid_renderer_raster(SEXP grSEXP, SEXP imageSEXP, SEXP xSEXP, SEXP ySEXP, SEXP widthSEXP, SEXP heightSEXP, SEXP interpolateSEXP) { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< XPtr >::type gr(grSEXP); Rcpp::traits::input_parameter< RObject >::type image(imageSEXP); Rcpp::traits::input_parameter< Length >::type x(xSEXP); Rcpp::traits::input_parameter< Length >::type y(ySEXP); Rcpp::traits::input_parameter< Length >::type width(widthSEXP); Rcpp::traits::input_parameter< Length >::type height(heightSEXP); Rcpp::traits::input_parameter< bool >::type interpolate(interpolateSEXP); grid_renderer_raster(gr, image, x, y, width, height, interpolate); return R_NilValue; END_RCPP } // grid_renderer_rect void grid_renderer_rect(XPtr gr, Length x, Length y, Length width, Length height, List gp, Length r); RcppExport SEXP _gridtext_grid_renderer_rect(SEXP grSEXP, SEXP xSEXP, SEXP ySEXP, SEXP widthSEXP, SEXP heightSEXP, SEXP gpSEXP, SEXP rSEXP) { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< XPtr >::type gr(grSEXP); Rcpp::traits::input_parameter< Length >::type x(xSEXP); Rcpp::traits::input_parameter< Length >::type y(ySEXP); Rcpp::traits::input_parameter< Length >::type width(widthSEXP); Rcpp::traits::input_parameter< Length >::type height(heightSEXP); Rcpp::traits::input_parameter< List >::type gp(gpSEXP); Rcpp::traits::input_parameter< Length >::type r(rSEXP); grid_renderer_rect(gr, x, y, width, height, gp, r); return R_NilValue; END_RCPP } // grid_renderer_collect_grobs List grid_renderer_collect_grobs(XPtr gr); RcppExport SEXP _gridtext_grid_renderer_collect_grobs(SEXP grSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< XPtr >::type gr(grSEXP); rcpp_result_gen = Rcpp::wrap(grid_renderer_collect_grobs(gr)); return rcpp_result_gen; END_RCPP } // unit_pt NumericVector unit_pt(NumericVector x); RcppExport SEXP _gridtext_unit_pt(SEXP xSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); rcpp_result_gen = Rcpp::wrap(unit_pt(x)); return rcpp_result_gen; END_RCPP } // gpar_empty List gpar_empty(); RcppExport SEXP _gridtext_gpar_empty() { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; rcpp_result_gen = Rcpp::wrap(gpar_empty()); return rcpp_result_gen; END_RCPP } // text_grob List text_grob(CharacterVector label, NumericVector x_pt, NumericVector y_pt, RObject gp, RObject name); RcppExport SEXP _gridtext_text_grob(SEXP labelSEXP, SEXP x_ptSEXP, SEXP y_ptSEXP, SEXP gpSEXP, SEXP nameSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type label(labelSEXP); Rcpp::traits::input_parameter< NumericVector >::type x_pt(x_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type y_pt(y_ptSEXP); Rcpp::traits::input_parameter< RObject >::type gp(gpSEXP); Rcpp::traits::input_parameter< RObject >::type name(nameSEXP); rcpp_result_gen = Rcpp::wrap(text_grob(label, x_pt, y_pt, gp, name)); return rcpp_result_gen; END_RCPP } // raster_grob List raster_grob(RObject image, NumericVector x_pt, NumericVector y_pt, NumericVector width_pt, NumericVector height_pt, LogicalVector interpolate, RObject gp, RObject name); RcppExport SEXP _gridtext_raster_grob(SEXP imageSEXP, SEXP x_ptSEXP, SEXP y_ptSEXP, SEXP width_ptSEXP, SEXP height_ptSEXP, SEXP interpolateSEXP, SEXP gpSEXP, SEXP nameSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< RObject >::type image(imageSEXP); Rcpp::traits::input_parameter< NumericVector >::type x_pt(x_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type y_pt(y_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type width_pt(width_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type height_pt(height_ptSEXP); Rcpp::traits::input_parameter< LogicalVector >::type interpolate(interpolateSEXP); Rcpp::traits::input_parameter< RObject >::type gp(gpSEXP); Rcpp::traits::input_parameter< RObject >::type name(nameSEXP); rcpp_result_gen = Rcpp::wrap(raster_grob(image, x_pt, y_pt, width_pt, height_pt, interpolate, gp, name)); return rcpp_result_gen; END_RCPP } // rect_grob List rect_grob(NumericVector x_pt, NumericVector y_pt, NumericVector width_pt, NumericVector height_pt, RObject gp, RObject name); RcppExport SEXP _gridtext_rect_grob(SEXP x_ptSEXP, SEXP y_ptSEXP, SEXP width_ptSEXP, SEXP height_ptSEXP, SEXP gpSEXP, SEXP nameSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x_pt(x_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type y_pt(y_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type width_pt(width_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type height_pt(height_ptSEXP); Rcpp::traits::input_parameter< RObject >::type gp(gpSEXP); Rcpp::traits::input_parameter< RObject >::type name(nameSEXP); rcpp_result_gen = Rcpp::wrap(rect_grob(x_pt, y_pt, width_pt, height_pt, gp, name)); return rcpp_result_gen; END_RCPP } // roundrect_grob List roundrect_grob(NumericVector x_pt, NumericVector y_pt, NumericVector width_pt, NumericVector height_pt, NumericVector r_pt, RObject gp, RObject name); RcppExport SEXP _gridtext_roundrect_grob(SEXP x_ptSEXP, SEXP y_ptSEXP, SEXP width_ptSEXP, SEXP height_ptSEXP, SEXP r_ptSEXP, SEXP gpSEXP, SEXP nameSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x_pt(x_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type y_pt(y_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type width_pt(width_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type height_pt(height_ptSEXP); Rcpp::traits::input_parameter< NumericVector >::type r_pt(r_ptSEXP); Rcpp::traits::input_parameter< RObject >::type gp(gpSEXP); Rcpp::traits::input_parameter< RObject >::type name(nameSEXP); rcpp_result_gen = Rcpp::wrap(roundrect_grob(x_pt, y_pt, width_pt, height_pt, r_pt, gp, name)); return rcpp_result_gen; END_RCPP } // set_grob_coords RObject set_grob_coords(RObject grob, NumericVector x, NumericVector y); RcppExport SEXP _gridtext_set_grob_coords(SEXP grobSEXP, SEXP xSEXP, SEXP ySEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< RObject >::type grob(grobSEXP); Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); rcpp_result_gen = Rcpp::wrap(set_grob_coords(grob, x, y)); return rcpp_result_gen; END_RCPP } static const R_CallMethodDef CallEntries[] = { {"_gridtext_bl_make_null_box", (DL_FUNC) &_gridtext_bl_make_null_box, 2}, {"_gridtext_bl_make_par_box", (DL_FUNC) &_gridtext_bl_make_par_box, 4}, {"_gridtext_bl_make_rect_box", (DL_FUNC) &_gridtext_bl_make_rect_box, 11}, {"_gridtext_bl_make_text_box", (DL_FUNC) &_gridtext_bl_make_text_box, 3}, {"_gridtext_bl_make_raster_box", (DL_FUNC) &_gridtext_bl_make_raster_box, 9}, {"_gridtext_bl_make_vbox", (DL_FUNC) &_gridtext_bl_make_vbox, 5}, {"_gridtext_bl_make_regular_space_glue", (DL_FUNC) &_gridtext_bl_make_regular_space_glue, 3}, {"_gridtext_bl_make_forced_break_penalty", (DL_FUNC) &_gridtext_bl_make_forced_break_penalty, 0}, {"_gridtext_bl_make_never_break_penalty", (DL_FUNC) &_gridtext_bl_make_never_break_penalty, 0}, {"_gridtext_bl_box_width", (DL_FUNC) &_gridtext_bl_box_width, 1}, {"_gridtext_bl_box_height", (DL_FUNC) &_gridtext_bl_box_height, 1}, {"_gridtext_bl_box_ascent", (DL_FUNC) &_gridtext_bl_box_ascent, 1}, {"_gridtext_bl_box_descent", (DL_FUNC) &_gridtext_bl_box_descent, 1}, {"_gridtext_bl_box_voff", (DL_FUNC) &_gridtext_bl_box_voff, 1}, {"_gridtext_bl_calc_layout", (DL_FUNC) &_gridtext_bl_calc_layout, 3}, {"_gridtext_bl_place", (DL_FUNC) &_gridtext_bl_place, 3}, {"_gridtext_bl_render", (DL_FUNC) &_gridtext_bl_render, 3}, {"_gridtext_grid_renderer", (DL_FUNC) &_gridtext_grid_renderer, 0}, {"_gridtext_grid_renderer_text", (DL_FUNC) &_gridtext_grid_renderer_text, 5}, {"_gridtext_grid_renderer_text_details", (DL_FUNC) &_gridtext_grid_renderer_text_details, 2}, {"_gridtext_grid_renderer_raster", (DL_FUNC) &_gridtext_grid_renderer_raster, 7}, {"_gridtext_grid_renderer_rect", (DL_FUNC) &_gridtext_grid_renderer_rect, 7}, {"_gridtext_grid_renderer_collect_grobs", (DL_FUNC) &_gridtext_grid_renderer_collect_grobs, 1}, {"_gridtext_unit_pt", (DL_FUNC) &_gridtext_unit_pt, 1}, {"_gridtext_gpar_empty", (DL_FUNC) &_gridtext_gpar_empty, 0}, {"_gridtext_text_grob", (DL_FUNC) &_gridtext_text_grob, 5}, {"_gridtext_raster_grob", (DL_FUNC) &_gridtext_raster_grob, 8}, {"_gridtext_rect_grob", (DL_FUNC) &_gridtext_rect_grob, 6}, {"_gridtext_roundrect_grob", (DL_FUNC) &_gridtext_roundrect_grob, 7}, {"_gridtext_set_grob_coords", (DL_FUNC) &_gridtext_set_grob_coords, 3}, {NULL, NULL, 0} }; RcppExport void R_init_gridtext(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } gridtext/src/grid-renderer.cpp0000644000176200001440000000227513524316120016133 0ustar liggesusers/* R bindings to grid renderer, for unit testing */ #include "grid-renderer.h" // [[Rcpp::export]] XPtr grid_renderer() { XPtr gr(new GridRenderer()); return gr; } // [[Rcpp::export]] void grid_renderer_text(XPtr gr, const CharacterVector &label, Length x, Length y, List gp) { return gr->text(label, x, y, gp); } // [[Rcpp::export]] List grid_renderer_text_details(const CharacterVector &label, List gp) { TextDetails td = GridRenderer::text_details(label, gp); List out = List::create( _["width_pt"] = td.width, _["ascent_pt"] = td.ascent, _["descent_pt"] = td.descent, _["space_pt"] = td.space ); return out; } // [[Rcpp::export]] void grid_renderer_raster(XPtr gr, RObject image, Length x, Length y, Length width, Length height, bool interpolate = true) { return gr->raster(image, x, y, width, height, interpolate); } // [[Rcpp::export]] void grid_renderer_rect(XPtr gr, Length x, Length y, Length width, Length height, List gp, Length r = 0) { return gr->rect(x, y, width, height, gp, r); } // [[Rcpp::export]] List grid_renderer_collect_grobs(XPtr gr) { return gr->collect_grobs(); } gridtext/src/raster-box.h0000644000176200001440000001140113510311136015121 0ustar liggesusers#ifndef RASTER_BOX_H #define RASTER_BOX_H #include using namespace Rcpp; #include // for pair<> using namespace std; #include "layout.h" pair image_dimensions(RObject image) { Environment env = Environment::namespace_env("base"); Function dim = env["dim"]; NumericVector dims = dim(image); if (dims.size() < 2) { stop("Cannot extract image dimensions. Image must be a matrix, raster, or nativeRaster object."); } // first dimension is rows (height), second is columns (width) return pair(dims[1], dims[0]); } // A box holding a single image template class RasterBox : public Box { private: RObject m_image; typename Renderer::GraphicsContext m_gp; Length m_width, m_height; SizePolicy m_width_policy, m_height_policy; // position of the box in enclosing box // the box reference point is the leftmost point of the baseline. Length m_x, m_y; bool m_respect_asp; // if `true`, always plots image with correct aspect ratio, regardless of box dimensions bool m_interpolate; // if `true`, interpolates raster images double m_dpi; // dots per inch to determine native image sizes double m_rel_width, m_rel_height; // used to store relative width and height when needed Length m_native_width, m_native_height; // native width and height of image, in pt public: RasterBox(RObject image, Length width, Length height, const typename Renderer::GraphicsContext &gp, SizePolicy width_policy = SizePolicy::native, SizePolicy height_policy = SizePolicy::native, bool respect_aspect = true, bool interpolate = true, double dpi = 150) : m_image(image), m_gp(gp), m_width(width), m_height(height), m_width_policy(width_policy), m_height_policy(height_policy), m_x(0), m_y(0), m_respect_asp(respect_aspect), m_interpolate(interpolate), m_dpi(dpi), m_rel_width(0), m_rel_height(0), m_native_width(0), m_native_height(0) { pair d = image_dimensions(image); // there are 72.27 pt in each in m_native_width = d.first * 72.27 / m_dpi; m_native_height = d.second * 72.27 / m_dpi; if (m_width_policy == SizePolicy::relative) { m_rel_width = m_width/100; } if (m_height_policy == SizePolicy::relative) { m_rel_height = m_height/100; } } ~RasterBox() {}; Length width() { return m_width; } Length ascent() { return m_height; } Length descent() { return 0; } Length voff() { return 0; } void calc_layout(Length width_hint, Length height_hint) { if (m_width_policy == SizePolicy::native && m_height_policy == SizePolicy::native) { m_width = m_native_width; m_height = m_native_height; return; } switch(m_width_policy) { case SizePolicy::expand: m_width = width_hint; break; case SizePolicy::relative: m_width = width_hint * m_rel_width; break; case SizePolicy::fixed: default: break; } switch(m_height_policy) { case SizePolicy::expand: m_height = height_hint; break; case SizePolicy::relative: m_height = height_hint * m_rel_height; break; case SizePolicy::native: m_height = m_width * m_native_height / m_native_width; break; case SizePolicy::fixed: default: break; } // can only do this calculation after height is set if (m_width_policy == SizePolicy::native) { m_width = m_height * m_native_width / m_native_height; } } // place box in internal coordinates used in enclosing box void place(Length x, Length y) { m_x = x; m_y = y; } // render into absolute coordinates, using the reference coordinates // from the enclosing box void render(Renderer &r, Length xref, Length yref) { Length x = m_x + xref; Length y = m_y + yref; // adjust for aspect ratio if necessary if (!m_respect_asp || (m_width/m_height == m_native_width/m_native_height)) { r.raster(m_image, x, y, m_width, m_height, m_interpolate, m_gp); } else { // do we need to adjust the height or the width of the image? if (m_height_policy == SizePolicy::native || (m_width/m_height > m_native_width/m_native_height && !(m_width_policy == SizePolicy::native))) { // adjust imate width if box is wider than image or native image height is requested Length width = m_height * m_native_width / m_native_height; Length xoff = (m_width - width)/2; r.raster(m_image, x + xoff, y, width, m_height, m_interpolate, m_gp); } else { // otherwise adjust image height Length height = m_width * m_native_height / m_native_width; Length yoff = (m_height - height)/2; r.raster(m_image, x, y + yoff, m_width, height, m_interpolate, m_gp); } } } }; #endif gridtext/R/0000755000176200001440000000000013764206775012326 5ustar liggesusersgridtext/R/read-image.R0000644000176200001440000000111213523126662014424 0ustar liggesusersread_image <- function(path) { if (isTRUE(grepl("\\.png$", path, ignore.case = TRUE))) { img <- png::readPNG(get_file(path), native = TRUE) } else if (isTRUE(grepl("(\\.jpg$)|(\\.jpeg)", path, ignore.case = TRUE))) { img <- jpeg::readJPEG(get_file(path), native = TRUE) } else { warning(paste0("Image type not supported: ", path), call. = FALSE) img <- grDevices::as.raster(matrix(0, 10, 10)) } } get_file <- function(path) { if (is_url(path)) { RCurl::getBinaryURL(path) } else { path } } is_url <- function(path) { grepl("https?://", path) } gridtext/R/parse-css.R0000644000176200001440000000415613567342161014346 0ustar liggesusers# Parse css # # A very simple css parser that can parse `key:value;` pairs. # # @param text The css text to parse parse_css <- function(text) { # break into separate lines; for now, ignore the possibility of # quoted or escaped semicolon lines <- strsplit(text, ";", fixed = TRUE)[[1]] # parse each line and return list of key--value pairs unlist(lapply(lines, parse_css_line), recursive = FALSE) } parse_css_line <- function(line) { pattern <- "\\s*(\\S+)\\s*:\\s*(\"(.*)\"|'(.*)'|(\\S*))\\s*" m <- attributes(regexpr(pattern, line, perl = TRUE)) if (m$capture.start[1] > 0) { key <- substr(line, m$capture.start[1], m$capture.start[1] + m$capture.length[1] - 1) } else key <- NULL if (m$capture.start[3] > 0) { value <- substr(line, m$capture.start[3], m$capture.start[3] + m$capture.length[3] - 1) } else if (m$capture.start[4] > 0) { value <- substr(line, m$capture.start[4], m$capture.start[4] + m$capture.length[4] - 1) } else if (m$capture.start[5] > 0) { value <- substr(line, m$capture.start[5], m$capture.start[5] + m$capture.length[5] - 1) } else value <- NULL if (is.null(key)) list() else list2(!!key := value) } parse_css_unit <- function(x) { pattern <- "^((-?\\d+\\.?\\d*)(%|[a-zA-Z]+)|(0))$" m <- attributes(regexpr(pattern, x, perl = TRUE)) if (m$capture.start[4] > 0) { # matched null value return(list(value = 0, unit = "pt")) } else { if (m$capture.start[2] > 0) { value <- as.numeric( substr(x, m$capture.start[2], m$capture.start[2] + m$capture.length[2] - 1) ) if (m$capture.start[3] > 0) { unit <- substr(x, m$capture.start[3], m$capture.start[3] + m$capture.length[3] - 1) return(list(value = value, unit = unit)) } } } stop(paste0("The string '", x, "' does not represent a valid CSS unit."), call. = FALSE) } convert_css_unit_pt <- function(x) { u <- parse_css_unit(x) switch( u$unit, pt = u$value, px = (72/96)*u$value, `in` = 72*u$value, cm = (72/2.54)*u$value, mm = (72/25.4)*u$value, stop(paste0("Cannot convert ", u$value, u$unit, " to pt."), call. = FALSE) ) } gridtext/R/recycle-gpar.R0000644000176200001440000000075213523037636015021 0ustar liggesusers# takes a graphical parameters object gp and returns a list of # length n of appropriately recycled elements from gp recycle_gpar <- function(gp = NULL, n = 1) { make_gpar <- function(n, ...) { structure( list(...), class = "gpar" ) } args <- c(list(make_gpar, n = 1:n), gp, list(SIMPLIFY = FALSE)) do.call(mapply, args) } # converts a unit vector into a list of individual unit objects unit_to_list <- function(u) { lapply(seq_along(u), function(i) u[i]) } gridtext/R/grob-zero.R0000644000176200001440000000075313523150712014342 0ustar liggesusers# An empty grob of no extent # # An empty grob of no extent. Useful when a grob is needed but no # content is desired. # @keywords internal # @export zeroGrob <- function() .zeroGrob .zeroGrob <- grid::grob(cl = "zeroGrob", name = "NULL") widthDetails.zeroGrob <- function(x) unit(0, "cm") heightDetails.zeroGrob <- function(x) unit(0, "cm") grobWidth.zeroGrob <- function(x) unit(0, "cm") grobHeight.zeroGrob <- function(x) unit(0, "cm") drawDetails.zeroGrob <- function(x, recording) {} gridtext/R/drawing-context.R0000644000176200001440000000507613605770274015570 0ustar liggesusers# create drawing context with defined state # halign defines horizontal text alignment (0 = left aligned, 0.5 = centered, 1 = right aligned) setup_context <- function(fontsize = 12, fontfamily = "", fontface = "plain", color = "black", lineheight = 1.2, halign = 0, word_wrap = TRUE, gp = NULL) { if (is.null(gp)) { gp <- gpar( fontsize = fontsize, fontfamily = fontfamily, fontface = fontface, col = color, cex = 1, lineheight = lineheight ) } gp <- update_gpar(get.gpar(), gp) set_context_gp(list(yoff_pt = 0, halign = halign, word_wrap = word_wrap), gp) } # update a given drawing context with the values provided via ... update_context <- function(drawing_context, ...) { dc_new <- list(...) names_new <- names(dc_new) names_old <- names(drawing_context) drawing_context[intersect(names_old, names_new)] <- NULL c(drawing_context, dc_new) } set_style <- function(drawing_context, style = NULL) { if (is.null(style)) return(drawing_context) css <- parse_css(style) if (!is.null(css$`font-size`)) { font_size = convert_css_unit_pt(css$`font-size`) } else { font_size = NULL } drawing_context <- set_context_gp( drawing_context, gpar(col = css$color, fontfamily = css$`font-family`, fontsize = font_size) ) } # helper functions -------------------------------------------------------- # update a gpar object with new values update_gpar <- function(gp, gp_new) { names_new <- names(gp_new) names_old <- names(gp) gp[c(intersect(names_old, names_new), "font")] <- NULL gp_new["font"] <- NULL do.call(gpar, c(gp, gp_new)) } # update the gpar object of a drawing context set_context_gp <- function(drawing_context, gp = NULL) { gp <- update_gpar(drawing_context$gp, gp) font_info <- text_details("", gp) linespacing_pt <- gp$lineheight * gp$fontsize em_pt <- gp$fontsize update_context( drawing_context, gp = gp, ascent_pt = font_info$ascent_pt, descent_pt = font_info$descent_pt, linespacing_pt = linespacing_pt, em_pt = em_pt ) } # update the fontface of a drawing context set_context_fontface <- function(drawing_context, fontface = "plain", overwrite = FALSE) { fontface_old <- drawing_context$gp$fontface # combine bold and italic if needed if (!isTRUE(overwrite)) { if (isTRUE(fontface == "italic") && isTRUE(fontface_old == "bold")) { fontface <- "bold.italic" } else if (isTRUE(fontface == "bold") && isTRUE(fontface_old == "italic")) { fontface <- "bold.italic" } } set_context_gp(drawing_context, gpar(fontface = fontface)) } gridtext/R/RcppExports.R0000644000176200001440000001025313764206775014743 0ustar liggesusers# Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 bl_make_null_box <- function(width_pt = 0, height_pt = 0) { .Call(`_gridtext_bl_make_null_box`, width_pt, height_pt) } bl_make_par_box <- function(node_list, vspacing_pt, width_policy = "native", hjust = NULL) { .Call(`_gridtext_bl_make_par_box`, node_list, vspacing_pt, width_policy, hjust) } bl_make_rect_box <- function(content, width_pt, height_pt, margin, padding, gp, content_hjust = 0, content_vjust = 1, width_policy = "fixed", height_policy = "fixed", r = 0) { .Call(`_gridtext_bl_make_rect_box`, content, width_pt, height_pt, margin, padding, gp, content_hjust, content_vjust, width_policy, height_policy, r) } bl_make_text_box <- function(label, gp, voff_pt = 0) { .Call(`_gridtext_bl_make_text_box`, label, gp, voff_pt) } bl_make_raster_box <- function(image, width_pt = 0, height_pt = 0, width_policy = "native", height_policy = "native", respect_aspect = TRUE, interpolate = TRUE, dpi = 150, gp = NULL) { .Call(`_gridtext_bl_make_raster_box`, image, width_pt, height_pt, width_policy, height_policy, respect_aspect, interpolate, dpi, gp) } bl_make_vbox <- function(node_list, width_pt = 0, hjust = 0, vjust = 1, width_policy = "native") { .Call(`_gridtext_bl_make_vbox`, node_list, width_pt, hjust, vjust, width_policy) } bl_make_regular_space_glue <- function(gp, stretch_ratio = 0.5, shrink_ratio = 0.333333) { .Call(`_gridtext_bl_make_regular_space_glue`, gp, stretch_ratio, shrink_ratio) } bl_make_forced_break_penalty <- function() { .Call(`_gridtext_bl_make_forced_break_penalty`) } bl_make_never_break_penalty <- function() { .Call(`_gridtext_bl_make_never_break_penalty`) } bl_box_width <- function(node) { .Call(`_gridtext_bl_box_width`, node) } bl_box_height <- function(node) { .Call(`_gridtext_bl_box_height`, node) } bl_box_ascent <- function(node) { .Call(`_gridtext_bl_box_ascent`, node) } bl_box_descent <- function(node) { .Call(`_gridtext_bl_box_descent`, node) } bl_box_voff <- function(node) { .Call(`_gridtext_bl_box_voff`, node) } bl_calc_layout <- function(node, width_pt = 0, height_pt = 0) { invisible(.Call(`_gridtext_bl_calc_layout`, node, width_pt, height_pt)) } bl_place <- function(node, x_pt, y_pt) { invisible(.Call(`_gridtext_bl_place`, node, x_pt, y_pt)) } bl_render <- function(node, x_pt = 0, y_pt = 0) { .Call(`_gridtext_bl_render`, node, x_pt, y_pt) } grid_renderer <- function() { .Call(`_gridtext_grid_renderer`) } grid_renderer_text <- function(gr, label, x, y, gp) { invisible(.Call(`_gridtext_grid_renderer_text`, gr, label, x, y, gp)) } grid_renderer_text_details <- function(label, gp) { .Call(`_gridtext_grid_renderer_text_details`, label, gp) } grid_renderer_raster <- function(gr, image, x, y, width, height, interpolate = TRUE) { invisible(.Call(`_gridtext_grid_renderer_raster`, gr, image, x, y, width, height, interpolate)) } grid_renderer_rect <- function(gr, x, y, width, height, gp, r = 0L) { invisible(.Call(`_gridtext_grid_renderer_rect`, gr, x, y, width, height, gp, r)) } grid_renderer_collect_grobs <- function(gr) { .Call(`_gridtext_grid_renderer_collect_grobs`, gr) } unit_pt <- function(x) { .Call(`_gridtext_unit_pt`, x) } gpar_empty <- function() { .Call(`_gridtext_gpar_empty`) } text_grob <- function(label, x_pt = 0L, y_pt = 0L, gp = NULL, name = NULL) { .Call(`_gridtext_text_grob`, label, x_pt, y_pt, gp, name) } raster_grob <- function(image, x_pt = 0L, y_pt = 0L, width_pt = 0L, height_pt = 0L, interpolate = TRUE, gp = NULL, name = NULL) { .Call(`_gridtext_raster_grob`, image, x_pt, y_pt, width_pt, height_pt, interpolate, gp, name) } rect_grob <- function(x_pt = 0L, y_pt = 0L, width_pt = 0L, height_pt = 0L, gp = NULL, name = NULL) { .Call(`_gridtext_rect_grob`, x_pt, y_pt, width_pt, height_pt, gp, name) } roundrect_grob <- function(x_pt = 0L, y_pt = 0L, width_pt = 0L, height_pt = 0L, r_pt = 5L, gp = NULL, name = NULL) { .Call(`_gridtext_roundrect_grob`, x_pt, y_pt, width_pt, height_pt, r_pt, gp, name) } set_grob_coords <- function(grob, x, y) { .Call(`_gridtext_set_grob_coords`, grob, x, y) } gridtext/R/gridtext.R0000644000176200001440000000064613604245113014267 0ustar liggesusers#' Improved text rendering support for grid graphics #' #' The gridtext package provides two new grobs, [`richtext_grob()`] and #' [`textbox_grob()`], which support drawing of formatted text labels and #' formatted text boxes, respectively. #' @name gridtext #' @docType package #' @useDynLib gridtext, .registration = TRUE #' @importFrom Rcpp sourceCpp #' @import grid #' @import rlang #' @importFrom xml2 read_html NULL gridtext/R/text-details.R0000644000176200001440000000623713604245311015046 0ustar liggesusers#' Calculate text details for a given text label #' #' Calculate text details for a given text label #' @param label Character vector containing the label. Can handle only one label at a time. #' @param gp Grid graphical parameters defining the font (`fontfamily`, `fontface`, and #' `fontface` should be defined). #' @examples #' text_details("Hello world!", grid::gpar(fontfamily = "", fontface = "plain", fontsize = 12)) #' text_details("Hello world!", grid::gpar(fontfamily = "", fontface = "plain", fontsize = 24)) #' text_details( #' "Hello world\nwith newline", #' grid::gpar(fontfamily = "", fontface = "plain", fontsize = 12) #' ) #' @noRd text_details <- function(label, gp = gpar()) { fontfamily <- gp$fontfamily %||% grid::get.gpar("fontfamily")$fontfamily fontface <- gp$fontface %||% grid::get.gpar("fontface")$fontface fontsize <- gp$fontsize %||% grid::get.gpar("fontsize")$fontsize devname <- names(grDevices::dev.cur()) fontkey <- paste0(devname, fontfamily, fontface, fontsize) if (devname == "null device") { cache <- FALSE # don't cache if no device open } else { cache <- TRUE } if (length(fontkey) != 1 || length(label) != 1) { stop("Function `text_details()` is not vectorized.", call. = FALSE) } # ascent and width depend on label and font l1 <- text_info(label, fontkey, fontfamily, fontface, fontsize, cache) # descent and space width depend only on font l2 <- font_info(fontkey, fontfamily, fontface, fontsize, cache) # concatenate, result is a list with four members, width_pt, ascent_pt, descent_pt, space_pt c(l1, l2) } font_info_cache <- new.env(parent = emptyenv()) font_info <- function(fontkey, fontfamily, fontface, fontsize, cache) { info <- font_info_cache[[fontkey]] if (is.null(info)) { descent_pt <- convertHeight(grobDescent(textGrob( label = "gjpqyQ", gp = gpar( fontsize = fontsize, fontfamily = fontfamily, fontface = fontface, cex = 1 ) )), "pt", valueOnly = TRUE) space_pt <- convertWidth(grobWidth(textGrob( label = " ", gp = gpar( fontsize = fontsize, fontfamily = fontfamily, fontface = fontface, cex = 1 ) )), "pt", valueOnly = TRUE) info <- list(descent_pt = descent_pt, space_pt = space_pt) if (cache) { font_info_cache[[fontkey]] <- info } } info } text_info_cache <- new.env(parent = emptyenv()) text_info <- function(label, fontkey, fontfamily, fontface, fontsize, cache) { key <- paste0(label, fontkey) info <- text_info_cache[[key]] if (is.null(info)) { ascent_pt <- convertHeight(grobHeight(textGrob( label = label, gp = gpar( fontsize = fontsize, fontfamily = fontfamily, fontface = fontface, cex = 1 ) )), "pt", valueOnly = TRUE) width_pt <- convertWidth(grobWidth(textGrob( label = label, gp = gpar( fontsize = fontsize, fontfamily = fontfamily, fontface = fontface, cex = 1 ) )), "pt", valueOnly = TRUE) info <- list(width_pt = width_pt, ascent_pt = ascent_pt) if (cache) { text_info_cache[[key]] <- info } } info } gridtext/R/richtext-grob.R0000644000176200001440000002704413616062271015224 0ustar liggesusers#' Draw formatted text labels #' #' This grob acts mostly as a drop-in replacement for [`grid::textGrob()`] #' but provides more sophisticated formatting. The grob can handle basic #' markdown and HTML formatting directives, and it can also draw #' boxes around each piece of text. Note that this grob **does not** draw #' [plotmath] expressions. #' #' @param text Character vector containing Markdown/HTML strings to draw. #' @param x,y Unit objects specifying the location of the reference point. #' @param hjust,vjust Numerical values specifying the justification #' of the text boxes relative to `x` and `y`. These justification parameters #' are specified in the internal reference frame of the text boxes, so that, #' for example, `hjust` adjusts the vertical justification when the #' text is rotated 90 degrees to the left or right. #' @param halign,valign Numerical values specifying the text justification #' inside the text boxes. If not specified, these default to `hjust` and #' `vjust`. #' @param rot Angle of rotation for text, in degrees. #' @param default.units Units of `x` and `y` if these are provided only as #' numerical values. #' @param margin,padding Unit vectors of four elements each indicating the #' margin and padding around each text label in the order top, right, #' bottom, left. Margins are drawn outside the enclosing box (if any), #' and padding is drawn inside. To avoid rendering artifacts, it is best #' to specify these values in absolute units (such as points, mm, or inch) #' rather than in relative units (such as npc). #' @param r The radius of the rounded corners. To avoid rendering artifacts, #' it is best to specify this in absolute units (such as points, mm, or inch) #' rather than in relative units (such as npc). #' @param align_widths,align_heights Should the widths and heights of all #' the text boxes be aligned? Default is no. #' @param name Name of the grob. #' @param gp Other graphical parameters for drawing. #' @param box_gp Graphical parameters for the enclosing box around each text label. #' @param vp Viewport. #' @param use_markdown Should the `text` input be treated as markdown? Default #' is yes. #' @param debug Should debugging info be drawn? Default is no. #' @return A grid [`grob`] that represents the formatted text. #' @seealso [`textbox_grob()`] #' @examples #' library(grid) #' #' text <- c( #' "Some text **in bold.**", "Linebreaks
Linebreaks
Linebreaks", #' "*x*2 + 5*x* + *C**i*", #' "Some blue text **in bold.**
And *italics text.*
#' And some large text." #' ) #' #' x <- c(.2, .1, .7, .9) #' y <- c(.8, .4, .1, .5) #' rot <- c(0, 0, 45, -45) #' gp = gpar(col = c("black", "red"), fontfamily = c("Palatino", "Courier", "Times", "Helvetica")) #' box_gp = gpar(col = "black", fill = c("cornsilk", NA, "lightblue1", NA), lty = c(0, 1, 1, 1)) #' hjust <- c(0.5, 0, 0, 1) #' vjust <- c(0.5, 1, 0, 0.5) #' #' g <- richtext_grob( #' text, x, y, hjust = hjust, vjust = vjust, rot = rot, #' padding = unit(c(6, 6, 4, 6), "pt"), #' r = unit(c(0, 2, 4, 8), "pt"), #' gp = gp, box_gp = box_gp #' ) #' grid.newpage() #' grid.draw(g) #' grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) #' #' # multiple text labels with aligned boxes #' text <- c("January", "February", "March", "April", "May") #' x <- (1:5)/6 + 1/24 #' y <- rep(0.8, 5) #' g <- richtext_grob( #' text, x, y, halign = 0, hjust = 1, #' rot = 45, #' padding = unit(c(3, 6, 1, 3), "pt"), #' r = unit(4, "pt"), #' align_widths = TRUE, #' box_gp = gpar(col = "black", fill = "cornsilk") #' ) #' grid.newpage() #' grid.draw(g) #' grid.points(x, y, default.units = "npc", pch = 19, size = unit(5, "pt")) #' @export richtext_grob <- function(text, x = unit(0.5, "npc"), y = unit(0.5, "npc"), hjust = 0.5, vjust = 0.5, halign = hjust, valign = vjust, rot = 0, default.units = "npc", margin = unit(c(0, 0, 0, 0), "pt"), padding = unit(c(0, 0, 0, 0), "pt"), r = unit(0, "pt"), align_widths = FALSE, align_heights = FALSE, name = NULL, gp = gpar(), box_gp = gpar(col = NA), vp = NULL, use_markdown = TRUE, debug = FALSE) { # make sure x and y are units if (!is.unit(x)) x <- unit(x, default.units) if (!is.unit(y)) y <- unit(y, default.units) # make sure we can handle input text even if provided as factor text <- as.character(text) # convert NAs to empty strings text <- ifelse(is.na(text), "", text) # make sure margin and padding are of length 4 margin <- rep(margin, length.out = 4) padding <- rep(padding, length.out = 4) # margin, padding, and r need to be in points margin_pt <- rep(0, 4) margin_pt[c(1, 3)] <- convertHeight(margin[c(1, 3)], "pt", valueOnly = TRUE) margin_pt[c(2, 4)] <- convertWidth(margin[c(2, 4)], "pt", valueOnly = TRUE) padding_pt <- rep(0, 4) padding_pt[c(1, 3)] <- convertHeight(padding[c(1, 3)], "pt", valueOnly = TRUE) padding_pt[c(2, 4)] <- convertWidth(padding[c(2, 4)], "pt", valueOnly = TRUE) r_pt <- convertUnit(r, "pt", valueOnly = TRUE) # make sure text, x, and y have the same length n <- unique(length(text), length(x), length(y)) if (length(n) > 1) { stop("Arguments `text`, `x`, and `y` must have the same length.", call. = FALSE) } gp_list <- recycle_gpar(gp, n) box_gp_list <- recycle_gpar(box_gp, n) # need to convert x and y to lists so mapply can handle them properly x_list <- unit_to_list(x) y_list <- unit_to_list(y) inner_boxes <- mapply( make_inner_box, text, halign, valign, use_markdown, gp_list, SIMPLIFY = FALSE ) # do we have to align the contents box sizes? if (isTRUE(align_widths) || isTRUE(align_heights)) { # yes, obtain max width and/or height as needed lapply(inner_boxes, bl_calc_layout) width <- vapply(inner_boxes, bl_box_width, numeric(1)) height <- vapply(inner_boxes, bl_box_height, numeric(1)) } if (isTRUE(align_widths)) { width <- max(width) } else { width <- list(NULL) } if (isTRUE(align_heights)) { height <- max(height) } else { height <- list(NULL) } grobs <- mapply( make_outer_box, inner_boxes, width, height, x_list, y_list, halign, valign, hjust, vjust, rot, list(margin_pt), list(padding_pt), r_pt, box_gp_list, SIMPLIFY = FALSE ) if (isTRUE(debug)) { ## calculate overall enclosing rectangle # first get xmax and xmin values for each child grob and overall xmax_pt <- vapply(grobs, function(x) {max(x$xext)}, numeric(1)) xmin_pt <- vapply(grobs, function(x) {min(x$xext)}, numeric(1)) xmax <- max(x + unit(xmax_pt, "pt")) xmin <- min(x + unit(xmin_pt, "pt")) # now similarly for ymax and ymin ymax_pt <- vapply(grobs, function(x) {max(x$yext)}, numeric(1)) ymin_pt <- vapply(grobs, function(x) {min(x$yext)}, numeric(1)) ymax <- max(y + unit(ymax_pt, "pt")) ymin <- min(y + unit(ymin_pt, "pt")) # now generate a polygon grob enclosing the entire area rect <- polygonGrob( x = unit.c(xmin, xmax, xmax, xmin), y = unit.c(ymin, ymin, ymax, ymax), gp = gpar(fill = "#E1F4FD", col = "#2523C1", lwd = 0.5) ) grobs <- c( list(rect), grobs, list( pointsGrob( x, y, pch = 19, size = unit(5, "pt"), gp = gpar(col = "#2523C1"), default.units = default.units ) ) ) } children <- do.call(gList, grobs) gTree( gp = gp, vp = vp, name = name, debug = debug, children = children, cl = "richtext_grob" ) } make_inner_box <- function(text, halign, valign, use_markdown, gp) { if (use_markdown) { text <- markdown::markdownToHTML(text = text, options = c("use_xhtml", "fragment_only")) } doctree <- read_html(paste0("", text)) drawing_context <- setup_context(gp = gp, halign = halign, word_wrap = FALSE) boxlist <- process_tags(xml2::as_list(doctree)$html$body, drawing_context) vbox_inner <- bl_make_vbox(boxlist, vjust = 0, width_policy = "native") vbox_inner } make_outer_box <- function(vbox_inner, width, height, x, y, halign, valign, hjust, vjust, rot, margin_pt, padding_pt, r_pt, box_gp) { if (is.null(width)) { width <- 0 width_policy <- "native" } else { width <- width + margin_pt[2] + margin_pt[4] + padding_pt[2] + padding_pt[4] # make space for margin and padding width_policy <- "fixed" } if (is.null(height)) { height <- 0 height_policy <- "native" } else { height <- height + margin_pt[1] + margin_pt[3] + padding_pt[1] + padding_pt[3] # make space for margin and padding height_policy <- "fixed" } rect_box <- bl_make_rect_box( vbox_inner, width, height, margin_pt, padding_pt, box_gp, content_hjust = halign, content_vjust = valign, width_policy = width_policy, height_policy = height_policy, r = r_pt ) vbox_outer <- bl_make_vbox(list(rect_box), hjust = hjust, vjust = vjust, width_policy = "native") bl_calc_layout(vbox_outer) grobs <- bl_render(vbox_outer) # calculate corner points # (We exclude x, y and keep everything in pt, to avoid unit calculations at this stage) # (lower left, lower right, upper left, upper right before rotation) theta <- rot*2*pi/360 width <- bl_box_width(vbox_outer) height <- bl_box_height(vbox_outer) # lower left xll <- -hjust*cos(theta)*width + vjust*sin(theta)*height yll <- -hjust*sin(theta)*width - vjust*cos(theta)*height # lower right xlr <- xll + width*cos(theta) ylr <- yll + width*sin(theta) # upper left xul <- xll - height*sin(theta) yul <- yll + height*cos(theta) # upper right xur <- xul + width*cos(theta) yur <- yul + width*sin(theta) xext <- c(xll, xlr, xul, xur) yext <- c(yll, ylr, yul, yur) gTree( x = x, y = y, xext = xext, yext = yext, children = grobs, vp = viewport(x = x, y = y, just = c(0, 0), angle = rot) ) } #' @export heightDetails.richtext_grob <- function(x) { grobs <- x$children # in debug mode we have to trim off debug grobs if (isTRUE(x$debug)) { grobs <- grobs[c(-1, -length(grobs))] } if (length(grobs) == 1) { # shortcut for grobs with just one child; unit calcs not needed unit(max(grobs[[1]]$yext) - min(grobs[[1]]$yext), "pt") } else { # get ymax and ymin values for each child grob ymax <- lapply(grobs, function(x) {x$y + unit(max(x$yext), "pt")}) ymin <- lapply(grobs, function(x) {x$y + unit(min(x$yext), "pt")}) # now return the difference between the overal max and the overall min do.call(max, ymax) - do.call(min, ymin) } } #' @export widthDetails.richtext_grob <- function(x) { grobs <- x$children # in debug mode we have to trim off debug grobs if (isTRUE(x$debug)) { grobs <- grobs[c(-1, -length(grobs))] } if (length(grobs) == 1) { # shortcut for grobs with just one child; unit calcs not needed unit(max(grobs[[1]]$xext) - min(grobs[[1]]$xext), "pt") } else { # get xmax and xmin values for each child grob xmax <- lapply(grobs, function(x) {x$x + unit(max(x$xext), "pt")}) xmin <- lapply(grobs, function(x) {x$x + unit(min(x$xext), "pt")}) # now return the difference between the overal max and the overall min do.call(max, xmax) - do.call(min, xmin) } } #' @export ascentDetails.richtext_grob <- function(x) { heightDetails(x) } #' @export descentDetails.richtext_grob <- function(x) { unit(0, "pt") } gridtext/R/process-tags.R0000644000176200001440000001177713605770360015066 0ustar liggesusersprocess_text <- function(node, drawing_context) { tokens <- stringr::str_split(stringr::str_squish(node), "[[:space:]]+")[[1]] # make interior boxes boxes <- lapply(tokens, function(token) { list( bl_make_text_box(token, drawing_context$gp, drawing_context$yoff_pt), bl_make_regular_space_glue(drawing_context$gp) ) } ) # if node starts with space, add glue at beginning if (isTRUE(grepl("^[[:space:]]", node))) { boxes <- c(list(bl_make_regular_space_glue(drawing_context$gp)), boxes) } boxes <- unlist(boxes, recursive = FALSE) # if node doesn't end with space, remove glue at end if (!isTRUE(grepl("[[:space:]]$", node))) { boxes[[length(boxes)]] <- NULL } boxes } process_tag_b <- function(node, drawing_context) { attr <- attributes(node) drawing_context <- set_style(drawing_context, attr$style) process_tags(node, set_context_fontface(drawing_context, "bold")) } process_tag_br <- function(node, drawing_context) { list( bl_make_text_box("", drawing_context$gp), bl_make_forced_break_penalty() ) } process_tag_i <- function(node, drawing_context) { attr <- attributes(node) drawing_context <- set_style(drawing_context, attr$style) process_tags(node, set_context_fontface(drawing_context, "italic")) } process_tag_img <- function(node, drawing_context) { attr <- attributes(node) height <- attr$height if (is.null(height)) { height <- 0 height_policy <- "native" } else { height <- as.numeric(height) height_policy <- "fixed" } width <- attr$width if (is.null(width)) { width <- 0 width_policy <- "native" } else { width <- as.numeric(width) width_policy <- "fixed" } if (height_policy == "fixed" && width_policy == "fixed") { respect_asp <- FALSE } else { respect_asp <- TRUE } # read image img <- read_image(attr$src) # dpi = 72.27 turns lengths in pixels to lengths in pt rb <- bl_make_raster_box( img, width, height, width_policy, height_policy, respect_aspect = respect_asp, dpi = 72.27 ) list(rb) } process_tag_p <- function(node, drawing_context) { attr <- attributes(node) drawing_context <- set_style(drawing_context, attr$style) boxes <- unlist( list( process_tags(node, drawing_context), process_tag_br(NULL, drawing_context) ), recursive = FALSE ) # word wrapping corresponds to width_policy = "relative". if (isTRUE(drawing_context$word_wrap)) { bl_make_par_box( boxes, drawing_context$linespacing_pt, width_policy = "relative", hjust = drawing_context$halign ) } else { bl_make_par_box( boxes, drawing_context$linespacing_pt, width_policy = "native", hjust = drawing_context$halign ) } } process_tag_span <- function(node, drawing_context) { attr <- attributes(node) drawing_context <- set_style(drawing_context, attr$style) process_tags(node, drawing_context) } process_tag_sup <- function(node, drawing_context) { # modify fontsize before processing style, to allow for manual overriding drawing_context <- set_context_gp(drawing_context, gpar(fontsize = 0.8*drawing_context$gp$fontsize)) attr <- attributes(node) drawing_context <- set_style(drawing_context, attr$style) # move drawing half a character above baseline drawing_context$yoff_pt <- drawing_context$yoff_pt + drawing_context$ascent_pt / 2 process_tags(node, drawing_context) } process_tag_sub <- function(node, drawing_context) { # modify fontsize before processing style, to allow for manual overriding drawing_context <- set_context_gp(drawing_context, gpar(fontsize = 0.8*drawing_context$gp$fontsize)) attr <- attributes(node) drawing_context <- set_style(drawing_context, attr$style) # move drawing half a character below baseline drawing_context$yoff_pt <- drawing_context$yoff_pt - drawing_context$ascent_pt / 2 process_tags(node, drawing_context) } dispatch_tag <- function(node, tag, drawing_context) { if (is.null(tag) || tag == "") { process_text(node, drawing_context) } else { switch( tag, "b" = process_tag_b(node, drawing_context), "strong" = process_tag_b(node, drawing_context), "br" = process_tag_br(node, drawing_context), "i" = process_tag_i(node, drawing_context), "img" = process_tag_img(node, drawing_context), "em" = process_tag_i(node, drawing_context), "p" = process_tag_p(node, drawing_context), "span" = process_tag_span(node, drawing_context), "sup" = process_tag_sup(node, drawing_context), "sub" = process_tag_sub(node, drawing_context), stop( paste0("gridtext has encountered a tag that isn't supported yet: <", tag, ">\n", "Only a very limited number of tags are currently supported."), call. = FALSE ) ) } } process_tags <- function(node, drawing_context) { tags <- names(node) boxes <- list() for (i in seq_along(node)) { boxes[[i]] <- dispatch_tag(node[[i]], tags[i], drawing_context) } unlist(boxes, recursive = FALSE) } gridtext/R/grid-utils.R0000644000176200001440000000423313526545641014530 0ustar liggesusers# various functions to help with grid functionality # from: https://github.com/thomasp85/ggforce/blob/cba71550606d18b4f4b245cb097aee5eeeec52a8/R/textbox.R#L290-L295 # code taken with permission (https://twitter.com/thomasp85/status/1160989815657119747) with_unit <- function(x, default) { if (!is.null(x) && !is.unit(x)) { x <- unit(x, default) } x } # calculate the current width of a grob, in pt # @flip are width and height flipped? # @convert_null should null values be converted or kept as they are? current_width_pt <- function(grob = NULL, width = NULL, flip = FALSE, convert_null = TRUE) { if (is.null(width)) { if (isTRUE(convert_null)) { width <- unit(1, 'npc') } else { return(NULL) } } if (isTRUE(flip)) { convert <- convertHeight } else { convert <- convertWidth } if (is.null(grob$vp)) { width_pt <- convert(width, 'pt', TRUE) } else { # If the grob has its own viewport then we need to push it and # afterwards pop it. For this to work in the general case # (stacked viewports, etc), we need to keep track of the depth # of the current viewport stack and pop appropriately. n <- current.vpPath()$n %||% 0 pushViewport(grob$vp) width_pt <- convert(width, 'pt', TRUE) popViewport(current.vpPath()$n - n) } width_pt } # calculate the current height of a grob, in pt current_height_pt <- function(grob = NULL, height = NULL, flip = FALSE, convert_null = TRUE) { if (is.null(height)) { if (isTRUE(convert_null)) { height <- unit(1, 'npc') } else { return(NULL) } } if (isTRUE(flip)) { convert <- convertWidth } else { convert <- convertHeight } if (is.null(grob$vp)) { height_pt <- convert(height, 'pt', TRUE) } else { # If the grob has its own viewport then we need to push it and # afterwards pop it. For this to work in the general case # (stacked viewports, etc), we need to keep track of the depth # of the current viewport stack and pop appropriately. n <- current.vpPath()$n %||% 0 pushViewport(grob$vp) height_pt <- convert(height, 'pt', TRUE) popViewport(current.vpPath()$n - n) } height_pt } gridtext/R/textbox-grob.R0000644000176200001440000003374713757770222015106 0ustar liggesusers#' Draw formatted multi-line text with word wrap #' #' The function `textbox_grob()` is intended to render multi-line text #' labels that require automatic word wrapping. It is similar to #' [`richtext_grob()`], but there are a few important differences. First, #' while [`richtext_grob()`] is vectorized, `textbox_grob()` is not. It #' can draw only a single text box at a time. Second, `textbox_grob()` #' doesn't support rendering the text box at arbitrary angles. Only #' four different orientations are supported, corresponding to a #' rotation by 0, 90, 180, and 270 degrees. #' #' @param text Character vector containing Markdown/HTML string to draw. #' @param x,y Unit objects specifying the location of the reference point. #' If set to `NULL` (the default), these values are chosen based on the #' values of `hjust` and `vjust` such that the box is appropriately #' justified in the enclosing viewport. #' @param width,height Unit objects specifying width and height of the #' grob. A value of `NULL` means take up exactly the space necessary #' to render all content. Use a value of `unit(1, "npc")` to have the #' box take up all available space. #' @param minwidth,minheight,maxwidth,maxheight Min and max values for #' width and height. Set to `NULL` to impose neither a minimum nor #' a maximum. Note: `minheight` and `maxheight` do not work if `width = NULL`. #' @param hjust,vjust Numerical values specifying the justification #' of the text box relative to the reference point defined by `x` and `y`. These #' justification parameters are specified in the internal reference frame of #' the text box, so that, for example, `hjust` adjusts the vertical #' justification when the text box is left- or right-rotated. #' @param halign,valign Numerical values specifying the justification of the text #' inside the text box. #' @param default.units Units of `x`, `y`, `width`, `height`, `minwidth`, #' `minheight`, `maxwidth`, `maxheight` if these are provided only as #' numerical values. #' @param margin,padding Unit vectors of four elements each indicating the #' margin and padding around each text label in the order top, right, #' bottom, left. Margins are drawn outside the enclosing box (if any), #' and padding is drawn inside. To avoid rendering artifacts, it is best #' to specify these values in absolute units (such as points, mm, or inch) #' rather than in relative units (such as npc). #' @param r The radius of the rounded corners. To avoid rendering artifacts, #' it is best to specify this in absolute units (such as points, mm, or inch) #' rather than in relative units (such as npc). #' @param orientation Orientation of the box. Allowed values are `"upright"`, #' `"left-rotated"`, `"right-rotated"`, and `"inverted"`, corresponding to #' a rotation by 0, 90, 270, and 180 degrees counter-clockwise, respectively. #' @param name Name of the grob. #' @param gp Other graphical parameters for drawing. #' @param box_gp Graphical parameters for the enclosing box around each text label. #' @param vp Viewport. #' @param use_markdown Should the `text` input be treated as markdown? #' @return A grid [`grob`] that represents the formatted text. #' @seealso [`richtext_grob()`] #' @examples #' library(grid) #' g <- textbox_grob( #' "**The quick brown fox jumps over the lazy dog.**

#' The quick brown fox jumps over the lazy dog. #' The **quick brown fox** jumps over the lazy dog. #' The quick brown fox jumps over the lazy dog.", #' x = unit(0.5, "npc"), y = unit(0.7, "npc"), halign = 0, valign = 1, #' gp = gpar(fontsize = 15), #' box_gp = gpar(col = "black", fill = "lightcyan1"), #' r = unit(5, "pt"), #' padding = unit(c(10, 10, 10, 10), "pt"), #' margin = unit(c(0, 10, 0, 10), "pt") #' ) #' grid.newpage() #' grid.draw(g) #' #' # internal vs. external alignment #' g1 <- textbox_grob( #' "The quick brown fox jumps over the lazy dog.", #' hjust = 0, vjust = 1, halign = 0, valign = 1, #' width = unit(1.5, "inch"), height = unit(1.5, "inch"), #' box_gp = gpar(col = "black", fill = "cornsilk"), #' padding = unit(c(2, 2, 2, 2), "pt"), #' margin = unit(c(5, 5, 5, 5), "pt") #' ) #' g2 <- textbox_grob( #' "The quick brown fox jumps over the lazy dog.", #' hjust = 1, vjust = 1, halign = 0.5, valign = 0.5, #' width = unit(1.5, "inch"), height = unit(1.5, "inch"), #' box_gp = gpar(col = "black", fill = "cornsilk"), #' padding = unit(c(2, 2, 2, 2), "pt"), #' margin = unit(c(5, 5, 5, 5), "pt") #' ) #' g3 <- textbox_grob( #' "The quick brown fox jumps over the lazy dog.", #' hjust = 0, vjust = 0, halign = 1, valign = 1, #' width = unit(1.5, "inch"), height = unit(1.5, "inch"), #' box_gp = gpar(col = "black", fill = "cornsilk"), #' padding = unit(c(2, 2, 2, 2), "pt"), #' margin = unit(c(5, 5, 5, 5), "pt") #' ) #' g4 <- textbox_grob( #' "The quick brown fox jumps over the lazy dog.", #' hjust = 1, vjust = 0, halign = 0, valign = 0, #' width = unit(1.5, "inch"), height = unit(1.5, "inch"), #' box_gp = gpar(col = "black", fill = "cornsilk"), #' padding = unit(c(2, 2, 2, 2), "pt"), #' margin = unit(c(5, 5, 5, 5), "pt") #' ) #' grid.newpage() #' grid.draw(g1) #' grid.draw(g2) #' grid.draw(g3) #' grid.draw(g4) #' #' # internal vs. external alignment, with rotated boxes #' g1 <- textbox_grob( #' "The quick brown fox jumps over the lazy dog.", #' hjust = 1, vjust = 1, halign = 0, valign = 1, #' width = unit(1.5, "inch"), height = unit(1.5, "inch"), #' orientation = "left-rotated", #' box_gp = gpar(col = "black", fill = "cornsilk"), #' padding = unit(c(2, 2, 2, 2), "pt"), #' margin = unit(c(5, 5, 5, 5), "pt") #' ) #' g2 <- textbox_grob( #' "The quick brown fox jumps over the lazy dog.", #' hjust = 0, vjust = 1, halign = 0.5, valign = 0.5, #' width = unit(1.5, "inch"), height = unit(1.5, "inch"), #' orientation = "right-rotated", #' box_gp = gpar(col = "black", fill = "cornsilk"), #' padding = unit(c(2, 2, 2, 2), "pt"), #' margin = unit(c(5, 5, 5, 5), "pt") #' ) #' g3 <- textbox_grob( #' "The quick brown fox jumps over the lazy dog.", #' hjust = 1, vjust = 1, halign = 1, valign = 1, #' width = unit(1.5, "inch"), height = unit(1.5, "inch"), #' orientation = "inverted", #' box_gp = gpar(col = "black", fill = "cornsilk"), #' padding = unit(c(2, 2, 2, 2), "pt"), #' margin = unit(c(5, 5, 5, 5), "pt") #' ) #' g4 <- textbox_grob( #' "The quick brown fox jumps over the lazy dog.", #' hjust = 1, vjust = 0, halign = 0, valign = 0, #' width = unit(1.5, "inch"), height = unit(1.5, "inch"), #' orientation = "upright", #' box_gp = gpar(col = "black", fill = "cornsilk"), #' padding = unit(c(2, 2, 2, 2), "pt"), #' margin = unit(c(5, 5, 5, 5), "pt") #' ) #' grid.newpage() #' grid.draw(g1) #' grid.draw(g2) #' grid.draw(g3) #' grid.draw(g4) #' @export textbox_grob <- function(text, x = NULL, y = NULL, width = unit(1, "npc"), height = NULL, minwidth = NULL, maxwidth = NULL, minheight = NULL, maxheight = NULL, hjust = 0.5, vjust = 0.5, halign = 0, valign = 1, default.units = "npc", margin = unit(c(0, 0, 0, 0), "pt"), padding = unit(c(0, 0, 0, 0), "pt"), r = unit(0, "pt"), orientation = c("upright", "left-rotated", "right-rotated", "inverted"), name = NULL, gp = gpar(), box_gp = gpar(col = NA), vp = NULL, use_markdown = TRUE) { # make sure x, y, width, height are units x <- with_unit(x, default.units) y <- with_unit(y, default.units) width <- with_unit(width, default.units) height <- with_unit(height, default.units) minwidth <- with_unit(minwidth, default.units) minheight <- with_unit(minheight, default.units) maxwidth <- with_unit(maxwidth, default.units) maxheight <- with_unit(maxheight, default.units) # make sure we can handle input text even if provided as factor text <- as.character(text) # convert NAs to empty strings text <- ifelse(is.na(text), "", text) # determine orientation and adjust accordingly orientation <- match.arg(orientation) if (orientation == "upright") { angle <- 0 if (is.null(x)) { x <- unit(hjust, "npc") } if (is.null(y)) { y <- unit(vjust, "npc") } flip <- FALSE } else if (orientation == "left-rotated") { angle <- 90 if (is.null(x)) { x <- unit(1-vjust, "npc") } if (is.null(y)) { y <- unit(hjust, "npc") } flip <- TRUE } else if (orientation == "right-rotated") { angle <- -90 if (is.null(x)) { x <- unit(vjust, "npc") } if (is.null(y)) { y <- unit(1-hjust, "npc") } flip <- TRUE } else if (orientation == "inverted") { angle <- 180 if (is.null(x)) { x <- unit(1-hjust, "npc") } if (is.null(y)) { y <- unit(1-vjust, "npc") } flip <- FALSE } # make sure margin and padding are of length 4 margin <- rep(margin, length.out = 4) padding <- rep(padding, length.out = 4) # margin, padding, and r need to be in points margin_pt <- rep(0, 4) margin_pt[c(1, 3)] <- convertHeight(margin[c(1, 3)], "pt", valueOnly = TRUE) margin_pt[c(2, 4)] <- convertWidth(margin[c(2, 4)], "pt", valueOnly = TRUE) padding_pt <- rep(0, 4) padding_pt[c(1, 3)] <- convertHeight(padding[c(1, 3)], "pt", valueOnly = TRUE) padding_pt[c(2, 4)] <- convertWidth(padding[c(2, 4)], "pt", valueOnly = TRUE) r_pt <- convertUnit(r, "pt", valueOnly = TRUE) # make sure text, x, y, and width have at most length 1 n <- max(length(text), length(x), length(y), length(width), length(height)) if (n > 1) { stop("The function textbox_grob() is not vectorized.", call. = FALSE) } # now parse html if (use_markdown) { text <- markdown::markdownToHTML(text = text, options = c("use_xhtml", "fragment_only")) } doctree <- read_html(paste0("", text)) # if width is set to NULL, we use the native size policy and turn off word wrap if (is.null(width)) { width_policy <- "native" word_wrap <- FALSE } else { width_policy <- "relativce" word_wrap <- TRUE } drawing_context <- setup_context(gp = gp, halign = halign, word_wrap = word_wrap) boxlist <- process_tags(xml2::as_list(doctree)$html$body, drawing_context) vbox_inner <- bl_make_vbox(boxlist, vjust = 0, width_pt = 100, width_policy = width_policy) gTree( width = width, height = height, x = x, y = y, halign = halign, valign = valign, hjust = hjust, vjust = vjust, minwidth = minwidth, minheight = minheight, maxwidth = maxwidth, maxheight = maxheight, angle = angle, flip = flip, vbox_inner = vbox_inner, margin_pt = margin_pt, padding_pt = padding_pt, r_pt = r_pt, gp = gp, box_gp = box_gp, vp = vp, name = name, cl = "textbox_grob" ) } #' @export makeContext.textbox_grob <- function(x) { if (is.null(x$width)) { width_policy <- "native" } else { width_policy <- "fixed" } width_pt <- current_width_pt(x, x$width, x$flip) minwidth_pt <- current_width_pt(x, x$minwidth, x$flip, convert_null = FALSE) maxwidth_pt <- current_width_pt(x, x$maxwidth, x$flip, convert_null = FALSE) if (!is.null(minwidth_pt) && width_pt < minwidth_pt) { width_pt <- minwidth_pt } if (!is.null(maxwidth_pt) && width_pt > maxwidth_pt) { width_pt <- maxwidth_pt } height_pt <- current_height_pt(x, x$height, x$flip, convert_null = FALSE) minheight_pt <- current_height_pt(x, x$minheight, x$flip, convert_null = FALSE) maxheight_pt <- current_height_pt(x, x$maxheight, x$flip, convert_null = FALSE) if (is.null(height_pt)) { height_pt <- 0 height_policy <- "native" } else { height_policy <- "fixed" } rect_box <- bl_make_rect_box( x$vbox_inner, width_pt, height_pt, x$margin_pt, x$padding_pt, x$box_gp, content_hjust = x$halign, content_vjust = x$valign, width_policy = width_policy, height_policy = height_policy, r = x$r_pt ) vbox_outer <- bl_make_vbox( list(rect_box), width_pt = width_pt, hjust = x$hjust, vjust = x$vjust, width_policy = width_policy ) bl_calc_layout(vbox_outer, width_pt) width_pt <- bl_box_width(vbox_outer) height_pt <- bl_box_height(vbox_outer) # check if height needs to be adjusted, and relayout if necessary relayout <- FALSE if (!is.null(minheight_pt) && height_pt < minheight_pt) { height_pt <- minheight_pt relayout <- TRUE } if (!is.null(maxheight_pt) && height_pt > maxheight_pt) { height_pt <- maxheight_pt relayout <- TRUE } if (relayout) { rect_box <- bl_make_rect_box( x$vbox_inner, width_pt, height_pt, x$margin_pt, x$padding_pt, x$box_gp, content_hjust = x$halign, content_vjust = x$valign, width_policy = width_policy, height_policy = "fixed", r = x$r_pt ) vbox_outer <- bl_make_vbox(list(rect_box), width_pt = width_pt, hjust = x$hjust, vjust = x$vjust, width_policy = width_policy) bl_calc_layout(vbox_outer, width_pt) width_pt <- bl_box_width(vbox_outer) height_pt <- bl_box_height(vbox_outer) } x$vbox_outer <- vbox_outer if (isTRUE(x$flip)) { x$width_pt <- height_pt x$height_pt <- width_pt } else { x$width_pt <- width_pt x$height_pt <- height_pt } vp <- viewport(x$x, x$y, just = c(x$hjust, x$vjust), angle = x$angle) if (is.null(x$vp)) { x$vp <- vp } else { x$vp <- vpStack(x$vp, vp) } x } #' @export makeContent.textbox_grob <- function(x) { # get absolute coordinates of the grob x_pt <- convertX(unit(x$hjust, "npc"), "pt", valueOnly = TRUE) y_pt <- convertY(unit(x$vjust, "npc"), "pt", valueOnly = TRUE) grobs <- bl_render(x$vbox_outer, x_pt, y_pt) setChildren(x, grobs) } #' @export heightDetails.textbox_grob <- function(x) { unit(x$height_pt, "pt") } #' @export widthDetails.textbox_grob <- function(x) { unit(x$width_pt, "pt") } #' @export ascentDetails.textbox_grob <- function(x) { unit(x$height_pt, "pt") } #' @export descentDetails.textbox_grob <- function(x) { unit(0, "pt") } gridtext/NEWS.md0000644000176200001440000000072013764204676013220 0ustar liggesusers# gridtext 0.1.4 - Make sure tests don't fail if vdiffr is missing. # gridtext 0.1.3 - Remove unneeded systemfonts dependency. # gridtext 0.1.2 - Fix build for testthat 3.0. # gridtext 0.1.1 - `richtext_grob()` and `textbox_grob()` now gracefully handle empty strings and NAs. # gridtext 0.1.0 First public release. Provides the two grobs `richtext_grob()` and `textbox_grob()` for formatted text rendering without and with word wrapping, respectively. gridtext/MD50000644000176200001440000001037713764504772012443 0ustar liggesusersa81cd9ab95ddcbff8baecc2c2e97b4b2 *DESCRIPTION 39276fab68fb6f733821e2f243383213 *LICENSE 0857ec650ec6eaebb76f061ff2d67a68 *NAMESPACE 1bf4e40b7bdc98e7bb9478baba344c96 *NEWS.md b35088f9df099335362baa60c94fabb9 *R/RcppExports.R 4527d6b21d7c1bb4691dde35ee928398 *R/drawing-context.R 33ddf7bf682842d3d1e18a29302f3c38 *R/grid-utils.R c75f2b410d48d061281f07fa280c3120 *R/gridtext.R d441b7c0ec23dc1e813e4efc421e4c62 *R/grob-zero.R 0c7639a9d088da143da79eccc7d103e7 *R/parse-css.R 6f2fdb69ac961039af6d814b61dae5d0 *R/process-tags.R ce37c3f6ac753f22d2a04ec8c9d4a965 *R/read-image.R 14f402b6578dc90c2ccc237fd5985d9a *R/recycle-gpar.R 78d868e02a776fa994f8ef9c1b0c35f2 *R/richtext-grob.R 993e909e3ca88ea9454c1b6c307fd560 *R/text-details.R a5bd8b3d723da070417833237a931d6a *R/textbox-grob.R 6b0f56d8948306eea2700dcd4f951c56 *README.md a424816ce3f3342cdd6254853ef2ff55 *inst/extdata/Rlogo.png 4b91fa975d9b9a1c36e4f0796fb94d9b *man/figures/README-unnamed-chunk-4-1.png b38817d8e9a2491e5dcfbb45a6f3b8c7 *man/figures/README-unnamed-chunk-5-1.png 444041d0d8b1efa2e4b96a01923728ff *man/figures/README-unnamed-chunk-6-1.png 5042b5bf2843319ddb68bb50144b4a12 *man/figures/README-unnamed-chunk-7-1.png f05ca7e8590471689cf00f242952751f *man/figures/README-unnamed-chunk-8-1.png 5c51af6b80be93402d056d83ef476473 *man/gridtext.Rd 670d7725c7c9e0122bac4bbbf9c49c6e *man/richtext_grob.Rd 725c355183ac984c7c998f776f344a7e *man/textbox_grob.Rd cb6e7b0f75173d5b42376d6ccaaae425 *src/RcppExports.cpp b768a47be0be837939ac29e35472f98e *src/bl-r-bindings.cpp d269719f4d854f1631ef0855544996c2 *src/glue.h 37903129a8e47d97fe238f007c62fd75 *src/grid-renderer.cpp 86aeb8f8c4adcbde508769c3489518aa *src/grid-renderer.h 7281f599adafac6197f3922bcaf8e59d *src/grid.cpp 0f83d99b161de0acd132a795b22061be *src/grid.h b2687846d89a7cf50e9981e1b027780e *src/gridtext_types.h fe9e6d3d255267f5c38d99d167b434e9 *src/layout.h 33db19a560625010d4974dc4ba5912fa *src/length.h 53d65bd42a588396dd17d073def9a721 *src/line-breaker.h 2fca8c188ccdc7e31da70a673d793992 *src/null-box.h 52a50bc433ecc83670967956e45f6f09 *src/par-box.h cb46811dca7dc0d664c1c215184dcefd *src/penalty.h 63c950ab0c0fbba33290ff8c42d25a1a *src/raster-box.h 2693123ffb9befabf5ca09312c049e93 *src/rect-box.h 089683ed503534bc7875101e82df704c *src/text-box.h 1231c98d2f4345736a6460869b5341b6 *src/vbox.h 76f9e928d1e06336050fbbf9afa18edd *tests/figs/deps.txt 8cde36bedfbb0387360e28927c14c6f3 *tests/figs/grid-renderer/mixing-text-and-boxes.svg e3b297cab87e270a47e6c1ef66c254a4 *tests/figs/grid-renderer/rendering-raster-data.svg c54d3410efb2abfb584a3df1898a1149 *tests/figs/grid-renderer/text-in-different-stylings.svg 69101d09beea13709591a9933de86b29 *tests/figs/richtext-grob/aligned-heights.svg b693385997bec7757f486fc9587d34aa *tests/figs/richtext-grob/aligned-widths.svg 2b84957ca9a04074e23f6fdb76bc888a *tests/figs/richtext-grob/various-text-boxes-w-debug.svg b6dc6571ddad9bd0893269af602d0d47 *tests/figs/richtext-grob/various-text-boxes.svg 29c1ec20c4fa45fd1701764204804eb2 *tests/figs/textbox-grob/box-spanning-entire-viewport-with-margins.svg ac3d25a8bb15c8576e1e1be197e5b0ad *tests/figs/textbox-grob/multiple-boxes-internal-alignment.svg f140c71bab32e5854d2eb9800abd1e49 *tests/figs/textbox-grob/multiple-boxes-inverted-internal-alignment.svg f2d116c67913c1bbb2c946c4fd631ffd *tests/figs/textbox-grob/multiple-boxes-left-rotated-internal-alignment.svg 8d90a80333398e45985b4afd082bd7dc *tests/figs/textbox-grob/multiple-boxes-right-rotated-internal-alignment.svg ac47bab71f229d8f1348f10a00972b0e *tests/figs/textbox-grob/rotation-around-fixed-point.svg 7cd33292d3bce2d48077d0ecc3b2a73e *tests/testthat.R da111b5d5ed9b70155ae02a92bae48f8 *tests/testthat/Rplots.pdf d9a7d5e06f4322eac9e14fddf2d6c974 *tests/testthat/helper-vdiffr.R 6bf9388356cea9d14c3cb4be5ff1fb95 *tests/testthat/test-grid-constructors.R cf5970a12ce52be28cc42be15e732631 *tests/testthat/test-grid-renderer.R 6206e103f5f343de2c97d386dc016672 *tests/testthat/test-null-box.R b93b0deb1702206c417ca18940baf01f *tests/testthat/test-raster-box.R 09f936a5a11699dbc2ed7f9e79a02c04 *tests/testthat/test-rect-box.R af6d45bda58c05f1bd9487d808d2ca1f *tests/testthat/test-richtext-grob.R 42e16762409b5e8fa25f28c8bdc9ff2b *tests/testthat/test-text-details.R e2e4fe3ecb718e533230918e79c98366 *tests/testthat/test-textbox-grob.R 31a17b8b353cc8350e21c402a6458f07 *tests/testthat/test-vbox.R gridtext/inst/0000755000176200001440000000000013510207573013064 5ustar liggesusersgridtext/inst/extdata/0000755000176200001440000000000013521725332014516 5ustar liggesusersgridtext/inst/extdata/Rlogo.png0000644000176200001440000004223113521725332016310 0ustar liggesusersPNG  IHDRp:sRGB pHYs  YiTXtXML:com.adobe.xmp 1 L'Y@IDATx} ŕnw]YDhK61qA шqK$:o6Ũqd Ѡw}rd{:UN:}ڮW~A-[[=`ҤI~_Zti;~J-8DpN9Ȕrs=!_F2E n[: )$:jkk3ŋ'FY,_^;$z{{G;b3B6u:~qt]g7JEܪpDq @x,}X," \ - _纉WJI꺅 sLw >]Uj„ Β% LK5pvkրxK,)U=dAO|u2ǣ>̍ihhtAU|J%\.B.rqAz$ʚ8K8~BOPwz{s_x-r|Oܦh;5\gΜYvWn+@(pQO3x≽Kq3>%G746:h %P, PT UAڀ?~/CWypQA| /JzdI&S.[vu43`R)(MsM2ӀPFK-*݁hoԩ'ҎX1+Gͤ3h KQ K e"C-QX3VdU w?`(,4T<(? 0iHA+&!X\Zq#?{GMMt@ʻ0Dܜ2e սkO@WLCOM&K>'^YW"â2K㎜*(!4:`$MjCjDM L)l-ӧNEӧ.ƍ-[+h*@83fHˆON(ݓ!ө, 0^vPfQ#JEJ8y0ACDD^'6x RK8`AdjVu\\;9 ”FMnd@w/mZyI3P.G 6,Y,n*+B.enZpXYKO SkQBX鄷%xCtCcԫtvvvv ӧO{DhإČn޼9{>^T蓯Loo{G RQM]?+D903)3 K֌GjBzn2H3iaEsM[Κ<9Gξv}JGHStI"9r94QsaWV*E[T +nFP&Z! …%\; ?J%2L$ή]**i *4OϏ-K>|x*Fqq虠YcHieG\Y 3HIIba`FfrT:fNG{F{EGڽi:ϧέ=vQAvf'`.}Ӫ'2Ktu1J!4, *SA1MxpĄ]&G B984кeuh~0IZ/I !lCR c_Q*SFkii`2O=`wa"acRaaho4{]iɧΝreD"20pUJ)anG`&Cqiъµ@$1 \LKgX` 친( =v5\NSOtOw7qH~Đ*(om ǔP* CIF:C}qSdѬS),5`eQ /6[hnnI@IN;m_(dg--s\. ) |g`n"IL9i;a%p`L BKypQ%7t<2j.,GPNyN\L8R,) ¯"{k\R*vڌ+QuUbmdvP}ŋWXPxg͗9bOoݲFتPMIԆpLw ]_ U&&9>KYߨ}! &oyhɀQC0suI?,Pk FWIAf9RAa2>Ӎi9IX4_a р(<˟~Р<Ҽ8BTy1JHf9ų L  Uф;Qc5l8qRep+$b"nE-g 9D]74I2̲2x kNDVRlA-? G |$Ne gAݠbH3XpFxJY|嗻ǏwV}ݣ w"4Ց y1e&vb)lc1N[駟6v(<+V<3(qQjzjQFr:#u#@5ACLHk[D6R,Du_noW?'[￿;, %_ۻlÅxa[Q/Fc=/^la]pMXi ѩk" 565e{3OSPFIR ZI΃MM#:;Pu2J^ &%,ۃ7_RQ Mbo[FA`orbow~j)0OXrg<_3 Ma;x JjE' Hxԥ=,v0o  ('1|m[2Q%b`V vqKXl~#3Wf73\Fy_yqsuV+mmwAKփiBdZբkBF%I dV}6_۷ܗVM  #, V.D"TN;HP⼜yoݡ#O]|醳K>bĘЂd60*jDZ.zIs朶*%OWBuʺ'yal(JxnWWhsQׄj>~9cqb҄QUy&MW5Day>0>2T-![/ОBmRZTՕSB)͙t&\pLb6 _]搨bB܈sm:.kll$}14EB3z`Bssed^JV?|[E!7li Ú  &{|wSٳ8#]B|704aO::ێ^-@oU$RV*&BN.-3P -/4qNW^? P!Ufuup7+~EbL<+lki,&?!-t;ZTK&rz@VvܸqLK0D\SxHT3Lm`Ut<o6OQxun7OaqMK^DVgPpçOYgX8/v,U`KrEHV20L\:>1vO$JG:-3dJp\ OK$g ұ򨎸,ugŐ)<BG 4\ABHyR旜O{Q_Y.Z וD⾊'M,oLHeScŁ@e  \\8~\εcKzap0vW-Y&&3g& N$5WZ7 25yqޭ./zYM@aL !)"ĢO N01B0y/@{ZG{c]*?!}Sq0\7̻Ux2868.TEĂD02ACpDÇ@f^3sij,ՏI}IV"8X!ѰSaq|sFౣ4tJtUVoKc㿄QdgGUgo 8KB;8 Ix =c('1_ IE NWQ ]01(<}Wn6P|ﴣ^O'ܠ(w-fz:T=*0̫"G ]JGK}81vgaXdg?xjgdOc#jB#:}6sN)R0~0TҞuE/)Lh`qTlAR> VJή{5vN?bH<@ -C lqO MnC+b?{ 3mݰx$10Ptc #Ѕ AD"Z ki*QF.%-} P00iP>?3P.pH|J:4`ɥK a8z7=x8$LDJT!+!D"|WBb/ZŒSF L K1JŹ瞱}LOKJ< Lۏ12H4+ EU 8BޝBJNR۹ҹd9BA[hW'kُB 񅒪PjH+XGM'b 0h*ql!!"AŒ9hKittfu~1!cBb -R!."r@&~󹫲T,Q,ZG^r*+˥E77*~4A/ՀPOsH2^'qD1{Ds?DƦaB.Tp- ɻX,榹uE(zӤ6K;0.kq.$˒E1$SA %*j,5'>iz)($?S%ܣ؝ᦛ ݤ:EjPh"h%кI[pį /o;0~ F]* ~>2 T 32R*a;0ECMPx2܆bGݹHjH,?^ݶpk'}pܹ|^H7EctmK j^uHA|w_6LmiIn)ʼn"'I$XRv8-5p{y1\29\ؤB~GKMUאͭuClԴ$8e[܏,=__P U|C}cr]zQQ.8*qD@$Oǔ!RBߕ[ĵ|+r٨LO-py& L<(UaU)9Fg)ܖxuVk/XNR$b0 uU (0GヤǝxHw"Ga= O²zseR>tp#ęmiqH:3d-0 -$uI r,>/IaPR0:cւ) :0-HGb#\fn!]WuEzݨFrv.[5I18 p?*v@x3 (2>PӍE窙%091u u+/ {$3ɚrl: #!a,0'_+ʍ$leF ~_}A=`~K/8PϮO=M浱؋M{Ne]h9'ҡ 0?n&rZ/lT0Fd5CrBm%X/=wbs9(66SW6dL9#X|OxTi7'.UbN?G^ON8isW)w˸|h :TRP0VZ 'x뺳0DChi+ k\9#񓰶=><8V X*uG$  c_%]uD+v?Otƪ6%8C+9~_a]nB3С-0ΘaI^.ٴi'd6D rw]{ֶ8<)]°xMyplV>8>X\f!(Aؾ*hd0 To坢yx/⤒lzcq(O9%`*\q:CE(C6=k~zE8Kj@[T)3E%.R` *0 O7V!{a0 ~|+.Q*Ej=k=kyA}@ak7q2Rw oɒ{.b#C$.CÏj332tV}h8'q(Qx"{Ogk\~ꫯn1# ǹd m;G|"b xr՗4e9N)N+"m$*!ctk^ & \*\d:=; 3k5> Ta&rT/(, qjG/kxküΎa*0Q",%i fDՁI' GUlO$w,Yr4Pltvgfc` "!qjG/<,¥:9'+rn1s\bmWl6q&8 ya>Ĭ7BIg2g!29ncwȒB+A1Z@ 2N| 3uKEMUţVf*qzU ;f}4=2ŗg12|ySfM ݰgAnā Zjƣf8pԶ9 U%Vr[дiS~_Bf@Hay֭21,C'5O,9-62hn NP^MdL`ʺ+;_4Eiˢ)]}!3+s$ 2HgRp~%bw͜; 9t`ol=_ṛ@=E0k*)`n~Ke޺|cc|pX5ZL!@ cpb@DN$*&N̅ %iD6Nwvp|-&P>cϧCƑ#wF_+8ca ΅b}ʱ,w:y_gfLd,#MzT>u_쟻֖#-,QqAb xpemHt!c8̚5Ca{im ȷ(cXr:abR $M9^L?hS#4",pW,s~ʩ[f383W̛w<ןm[ۛ7:^[z=bW.@y v!<= 4IаϞ}3$ 7sF eC>rg̍6`bxS|;В-$^5N/ƥ,@]n*]GܚWE&jTy"dw{>Snhq#FTf.*`_Qf uN|Ͱ#;r ̙< m`urȁ_{'| dX2Uɷ 6q@GYWP8D\֙X/e;&$\1Zʫj¦愺Q6zFyZ4fXͻQ'p3aF %@$DU'w9w=oT0\FUEep]nl2:8<2_,9܂3"ﵵs?z| D(uY`wO2q+hXr>8ؽ*++]zq^SܾƦpA唙O76/”o <[L&{S't};8=OacsEDȅ汫?ka/닷~YP? =l<-LJX%ga,tХ>W fK%ˏzRcVR%կ(Mԍ;8pK(%eLИ+VX]ʴw>V P}H0S/^[_(5qѳLAo VOq9{'Q6Obw= /\/`0 e Fk@aS!aRb1ܲ!N^v;+TUHjʝmyszXNJH.7~3~-Q-0WmtCG\:zTo~*ҏZc#$Q,l͗|8:MyXqgό0m#}"nsagG?`]i2^#(O|^A5.D¤˧&eACc0K8^7YM%0.L Zy&*4hn߫wftxx:9g\'O415˵_9eY2xJ1 si9ŝ2?)ot`a=Z9~"b>QE AT.a4c/@;*U0CSKd"jt9r.积wF&DyT+pHC!;‘04V 8u.66{p.@![@0p$O?nos>%p ,[~j5"AS.]`R O3% ȸb pV:y_ׁ]=6nd͍z~IFbFb!%1W -?`Ro4Yu'FeD2Mۙg_#- ,w}%s§8GNY)Ul+Gma҂DD3q%>` b})iypMҕ0c[Zql|rٿ 6Z."E(MiC?AKfw)ۥeiNfVC0IetOtd8${a@ AXun( •A # N bny1gh#Zָc][`lm l+ L$lN0idi't~Ѿz,0!'H&78Xs^Ô VC ERA҈~FCĺVi Ј^SH83G:޿)>*h ?G]Asx?%|dm0>wGX2QPtLjK:7i"g/y~g}ߏp^s@ 8pjlhL%OBq5{|xeKh(xYơB5H "wC V=~t/yƾ+cžcΓ$Fs=puŀ_ _ M6howsq$,"5 n#@!׶D?JI^o| ɷd=>QLv)lV+HO#r#_L,Ә=<_+=<xmK{f7 J}K7~m6-dzӋBxÅ%( ]; < El z}?NX$ 㘱D5i>K^0~ȱUv{н#[sy ȢIˍٖeQ0+^*-eV>>нB|KVzFwPxB:0Y9!7ov k@}bXq)p/`Gt h3Qr** ӄ;4#0cq?]8Ѻ['l\ z?F}6pjb0Q+ TV /*2AZHIRZ#z[J 6|}[SON:*YP  cw19N2v\u(*Zxh>j\_8Z-*%CMXȕh5|U 'WvSvp V;y"5;Y\J]KdIu2 O cEo4j2©xO P(PX<45j_?8T; |A|{cd)3LլZx<0#u׾a3J`V,v+!CGЭĀn4Ty| ^x%$\tMD@_a3*2Bn'<ŅEWjRIDAT΍8BѴ@1&Kşw]gO=&@$먾*Z6^\RqUy'TʿN5plc,9_^WW oc =^_UBXmAqHЫ7Kn'09Bx4 Q쾿ϩ)#IP\1pH8v aLlH ]m|mz&1Dn֛NZPwrY3L%g\C~Ɍ!T}X|ZFgª5{J0Wt}PHq3ZIʤ WpO+AKEsH_\~ 't_>yc^ȓz9tkXe*yxڜbhgQc/]5* _״'/)JVPT|ͻ240)gЂmx㉋>K{^mՌaBaexC'xىT V{:LSx;MxmrrS{DtQŭ^Aٴ$J(,3^q(B*>Bw+Fm!b[Un7J.b&f uw]P{lT͎yi8nn1=EIENDB`