gridtext/0000755000176200001440000000000014311055377012112 5ustar liggesusersgridtext/NAMESPACE0000644000176200001440000000110614310622556013325 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/LICENSE0000644000176200001440000000005414310622556013114 0ustar liggesusersYEAR: 2020 COPYRIGHT HOLDER: Claus O. Wilke gridtext/README.md0000644000176200001440000001533414310625361013372 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://lifecycle.r-lib.org/articles/stages.html#maturing) Improved text rendering support for grid graphics in R. ## Installation You can install the current release from CRAN with `install.packages()`: ``` r install.packages("gridtext") ``` To install the latest development version of this package, please run the following line in your R console: ``` r 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. ``` r 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`). ``` r 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. ``` r 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. ``` r 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()`. ``` r 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 received [financial support](https://www.r-consortium.org/all-projects/awarded-projects) from the [R consortium.](https://www.r-consortium.org) gridtext/man/0000755000176200001440000000000014310622556012663 5ustar liggesusersgridtext/man/gridtext.Rd0000644000176200001440000000067114310622556015010 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.Rd0000644000176200001440000001633014310622556015663 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/0000755000176200001440000000000014310625361014324 5ustar liggesusersgridtext/man/figures/README-unnamed-chunk-7-1.png0000644000176200001440000010331014310625361021022 0ustar liggesusersPNG  IHDRz4iCCPkCGColorSpaceGenericRGB8U]hU>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x c8eXIfMM*i_@IDATx 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.png0000644000176200001440000011011514310625361021024 0ustar liggesusersPNG  IHDRz4iCCPkCGColorSpaceGenericRGB8U]hU>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x c8eXIfMM*i_@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*>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x c8eXIfMM*i@B0w@IDATx\U=|'eJ+ik8a#`Ÿ &s9 &8cxכW'ιUuOKM4~r:[Jp&'B@-@꫺*B@G@H_! B`! :,B@; B@l9D#WB@ }B@-Ж{B@! B@!r\B@! DB@! "@[B@! B@! C@h=ruX! w@! rmG ! "@! [-a! B@H! B`! :,B@; B@l9D#WB@ }B@-Ж{B@! B@!r\B@! DB@! "@[B@! B@! C@h=ruX! w@! rmG ! "@! [-a! B@H! B`! :,B@; B@l9D#WB@ }B@-Ж{B@! B@!r\B@! DB@! "@[B@! B@! C@h=ruX! w@! rmG ! "@! [-a! B@H! B`! :,B@; B@l9D#WB@ }B@-Ж{B@! B@!r\B@! DB@! "@[B@! B@! CuXBBT*e$?O`3 ;3695mSS A N,HB rM<"iu; uwë߂'R8̪y.%%gM=H'j4,..&~g`p5F!_E1B` Yd Idflfgr&&mZj^)9CC2K.=Й@~B'_ᆸ\`p֘ȷ(G 6F@hG RDh?D444h#C -Q7@fM1i kkIn!P'm58$<Қ…1_Xt,,"Hi Ai)RyTP9)B=x$+@A@zhe~#p-{AJb>ϐe 9iK\{&Vc1*zd7I-6j3]s,Vk`CP{,0hRpVn 0&$x avO#Gt,tϜ UcJˆa\CITYszӽ=]6<4d7޸5P1W/"@WﳑdBnp&!9?6n_g.ck+`;UW$=% |)g~q}CyhJܤHBBdgƔĀ6b!W &~ܾFG 7qCjX$ !p" t>I&6~hsS6 mOJj{,cHD$ %YVdIq 1ec1ʙ9ʓBr R<1TɔzT>4O l{6}::m ߇= c_\M]MOC:"@B%Sv Paqwa.$ґ)EHpV  yCE;e))=6P* j)"9-gʩ),̤rS]ݝo^ۏ^,+䄀$2‰KO: [T5>(a8=y 1c XJ2Μ7dIX:kʲT/w_,%HuDo@<Xs龁^a۱s-vcHN9H !Psԙsءy71tќ+8g$>牁GTd*΢,; ~M81ѓa;HxWEܓ+w?6HUP$w cc~V{{z߾=v͇PǕOF# '@?:z석>xq0GŸT>~f\,JǼMֆ咍RHg xx$`(/>ݗHFDz^u$o1",=X?`'B8rc]v-mǎƴh"@ ] Gv=SS3vVpG@:ρ; a,2V B\z,U}, F%ӑ%g)2 srJ؛$ЎT}xso exF8#$Rbc%dG<¡z%p`݁]ჶ@"B@F@˕^c:zM |MC|@c i}@b?ŕˉHu6JZ%SV/-GBS ,;o6re7yȦQ# xs3^KEA'`4 Bv֊TalEJQ8'U Bi(.E8CRJKIjϠB~k$6" 'ywq.@t6& <q@8'Sv?8b>^L]9U$ήnI2<ª͚qVW;Pk+ F͸ /(BXq6x$)ii؂3ʱ9 )W XL"-#0eDeBbhጱN_v ()B`|Ղ pi|y;q"VmH1HTH3_%=Ʀ,`=Qq$/)[fvhr@W.N_ACHz:qK qIu:sg}I*@Rn  $LҎ[g Oǂ)1=Z@` } Vqǭ֏n` s[v))=j,*4>a\r81:7g1*rاp|hص 8[qh;Y{KƻF1agI$D$N$;=   :$t<6c׮gރb.B`s\|U E 'N'ŴNnm1(P$$~IQQ' ̐ϟ<dpuܐ4CӃ;F`Jq(ozZS67)2|hcui;u,ݴyDX *9zS @mۋe>688 T | F@hUuBPpY{ȓ69=cKpz8B:睒i*Nuut.cX\7! 6 SU z#@s~l cƩE,u [Scy {A!PE*Ґ%h}k&p Ħڸt؞;mk8<ᕶ5jhC4)=c6 b4 2ݶI|(4|!z@1=kz6R_# tyC\p cX~¸ZFW^s8}̐?>i!5ąr$-z}!L[T<~0pexGlrbڦg}ՙ0~?>6h6t q֞Zm^'p'N3;uχT^' 'ʃKb1p˴>^6yݖeƼmXᅤ-htВcG z1U6ylQPJUL#ho {}۷ωHЖuB@N@!QpYڝ ``Rz&yBHk/bfO9[KC]r@v;,b!@4>{l~ i M"9){*@0 W'ô`U]06;nk}ՎʼmȈ|KذQ:ul+O4'4c?6B`8,UthX{+g'2eC{E^OX 8psu륭.7t]"w% vei A^4ǬI`~KPpg\,E]eSp$8j`8M$9끦M7=>eozݼoey۰=఍vVLf?]Pf}Ŵ 8W#OsM[6rB@l kUT8p/A%0ݧ&ܚF GqNN3jFJڳgqCIs' 2,v/"P}u%G!0MX<.~3cǺN 34Cf])\6 "4a{l1{Ν;ovHVQ”.>.N=o<"4L<ر62Bc^)B䄀X/"@EP@ S` c>s No2<= ==#E1Bo")=wC Vydۮx^R<@UWJ"gyP ֪'iJAA] 83c1Ȕޝw`jڠ`MQ W*5 5J\U$Fߤ rWT,LJGXRl%juZOO>pW Z}f&,_ŏ-b31_U`hg}_F-POOn^.B@8"Pp!p#j~@\c(:l砌#}?$y1ά,Ҩ󠦧ϴYJT{,VwJ+"?UeCSdcϓ-q%Sޱ9Ԁ`Lpg#zrp0nA洞#"~!ec{܊ q׮lۛB`)L͇pulHt[=EfYR\+#2_Kg=͇>A۟^ϫiPΜ=$' !~D֏jm=@1UBSɌ@1Nx^O ⦆y qGGla|:;65IE&Vr? d=] .W+:%eLL`uok.>7O4O3;wnвuczaOaS)b?e-X3"@kN@}Hfs8>! a%SzCYH!_HC<,S^k}J_OܱÆ|>,i S_ݶS_9i}Su.F~+O ))Y*S^rNEt.oΛ$ X6ko);y}/>!9_(]iin}8}=MPLOk) 8%UFd]I~P2{dEBkX7Cݾ,MxsgX퇠Yl< 9! ևSi!p!eaNDzB Z[Z{q){NH.r4pi4Brms1*ermnTRyPnCCc9R7"9[A=Q-\tGF8B)[QQEB`&QlD]Q(Q.(Y'iLWW6wj*S\,=ƾPkkÞuǰ u1s.Wy $ f;?dy~=rz zB$ORK;4@6dmnSCi*kx+\"ڂ !.D G5P0R 2Ûڇδ[3)ϝXͺ6_7 Be *C݊rWkdHra}nم ǙdE;~f>86.tww#|$s/~VF+$PB@R(M\pO_Q0 1I Dܲis@dgc<]}IPQ Idd[&W4 n˭6vb*!ln4ˣv, S5 *S byyi.ə4O$Q()-/=T}"vJrB@?WgÑ|""qpQI%L yi!54b\2 ΊmlR6jȋTG5bV L&?ieF7_Ɨzypk-W|"Z٫j}І̎mvMM8RF1.,CL@r%i\揪i4]\.z+,儀X"@O#І)x\ Ljbt䦜I8Ayg!nw$vg%SVede̗B_Y$&ԌL.{|:&;eX ?RK6.T]"m;QV/0wBN!O#ЁM y "~j 4rs{ 1w. oEb+(8MK9}ژN06!:9 oL,Mzuh|+ :%n}^&C 6q"@5" S1!P/xRxh>1cmOdcHRZT!FzRZ[Ņ[XháG Wv?ޱxǯ4;r mF36Ŧ{D&j {2PP &幀Oܸσ @}J.ց:SQ!P*<:A<6 &̴!.B]IyzVIX$㜥\,ɽ;rrf̕+ʻIDq,3x1iкBe,] M* 5a䄀X"@O#@O_o/HPv|#Olp0͆]04"38aKC;@RQ&삻:nH))M-jPƒ~@{| N&WTp_kӒpm|x;i|(Vqjlt6>A"@+WbD2F:a@ Q~\^/%$bk + g 92|Mdv.~h"KgT|T䂽GSN +t e\~B v؁]}Ձe b&&&` [,քBeIpGGm1煅g^!zdzS4&ߡƯafS:"˚2004 qi%QAt} V<գۜ.pPL+9kti >hVg %Lhg9mQyw_9˖#ܸޱnծq mn~b[pΝ~X-DCTlZ7! V E '(>ӡ,j9ǩ, KlS`.6_mzh2jYL:z7`lcv6]˧3ozIəO{CcOs8T@<**Wh园 <[p/lzՏOԴt0ƈMgL3&[DG]/p~;~ښy:<[X-xEB$vy6)v,n6ID̸IF+ĨL`W >>e' ,2oҐUT B\glZ `h591Q\N^ѹlvֆ)-Nϱ'mnf6hyP !MnQPP5# fTP e'm{ŀec8['?.V|#aT˷|f/`5gV/ôQLh"X\:ajo5`|dː%_v?&$-7 aCi#G` l܍x{;@ 3WuBttv>g&0]`(~Mؗ`Oe.X-e=Y,z?Lr^,_r\~0c>y{yOn`NZ~Μ9}84lg}lʨ'6i6G"w``,V(ai@.o2-ZYS8Ua$B8ֵ:eKq鞊$—{ȏ[V׽2τ+0exNh؞ns;y=УLk^tuwAl߾5pnB` x}!@B-Ѝr4:0h8lW<|,}q{1%6k7|ȧ6 (Ne Ou>*]Ra'HLTWK:Pl/|~OoF{@hܹg@Af}tC+SFV>!QmG8vǎQLUMyX0B܀O[ >qy}Oh۰mcq)kl2\)oBI~rm[}I ƒ!GT=%ةg|^D.&Z )HR_>^VDTVN!ë,w`5jO=8bϽsnq>>$>t<ܹsv}c>6UySV}n('! yتf!P]NS&& q30jc7CHtg|`;.7CCR2~DvALmk!F2~Tŭh:N$+豃{qﳛ -oA,ooA_jΜ9k?b g ؝wfvVۭP.ZL$nl׮CO]8-A؎Okh}Br9Sb- )1t:{qЇ#%yn6ՒHI )}:de?!3$8wً80h{GqN[OҚ8'477gǎO|0PӄExoÆ\TnQ>! 6 BV :"1@D:aO؉'m@ )nHd'΋W&8:up cE"{7xq+I ̛W9WN楉:9c9ݮS'^YOl>y;rjD=p_P w=rI)mb4znmmϰحrg('@}jEl:nH;Mhm}JHiLU-[a{q܇ OT U>e2E"{s[ݣO>5޳xj~7Zf0o,4JkbHS $?;[ |KM{ڡs{wށE,ov˜L{Z%Vy=)wb}=8IQB@l0"@ D h}›a\҆DGiNzD/8?3Y> mF`KX4@Fڍ |j i:[x?ȪK9OJh]T{;NYiOHasXu+hPVn?2awpƙm8ڧ&p0xOE !PDCOXUtP+UZ'^%F'AqͽeǸƸX"F!n\A~&'qTǔNDZ1zv];wZO/SK.E.<=F3JR3t4HKr!#A'? .l>Yd?Y:쎡*X[3H'#i8xBbA.B@F@}$K'wKmO&I @sî>{;l׶@0u[mo|Q;7 ؝C|E*,A#9\ö 穰[jn5br# !PDꁲuF i8v8}{X_o=#v+c"E" lta%OQ!=B aH|QKK SZ>8!O4Cf/cG¤TpzWEI˙chWԅ)D{; [/C0zI;#njE=vjXX"2&mk3G񉸃4m  C=٠i}bY>"2o+%͘ Z~E9cvN1dU)Gx~4G?4nt7i b/l-.ZDvƴ\Ks̀,ڑ^}T B^\()еS_{?l[t q9Te !P7DE MN#g6) >116OmKFɾ%ʏpFr=SqXǥ@baR%M>ۮLNtS8k5AG- ;XczN7 !ZWE s |2g{$#[?xFpg( E@q!Ԁqom8+ Azlr{ynt3})hl@_1cz4|T }ZXo*QYqAn LLMZgBr +#pKΒSm61Ms^LN6H纈,Lyl:76ko {SvwB15w*@%!:;;AڰwPݻˎ8~ ;;Wul2+>PSq. LG 'µvhwwEe %+5UW=Ĕsv#:jB`dU!IZ8ĉ~)Ʀ'}:m8(DM DBV̀'2B5ZD`U\HJ=llc@IDAT/Bݬ>$e l]vrfZ9~\ ePIR>{Pyh`>Bvۍ)Bp !PDꃳZM{C9s)Ih5*YiWqI$>' ̑R@ B,T0StRd&#%2ES$8Ei0Napt'X{T6r'VI.xZ̐'N-c|vn벛pw)(VHEOH84 Q 㫺$;IVKc vQh7aH-L%Q]IKE|9?W|rdG)F3XQP! 6MUU !pqh6ߛrI)P\4QEdګUZb;^=/w `z mxvWTTVg+*--?ko1)pG$9!  mf_'1=ܓ ' ~{Y]ęϚ__[S ģ Wn=eUTSe,6yX,W#qg=oݼOAR-!mEreD"PS0_cή\~3OEeOͅP2TQwtg z7qj W, R?`YO<Şx {;ޱT|7|n3߉)f%#U"_YN֓Ae@Ooo5PZl uu|:--1;;'75UD{+]QCv^{ˆ C*Ĭ__׾"|ղU[O6]v4@??m?#?b'|~~ȗܦ]//ڗcY{ֳ~'ĈW嚗m۶ƈI^qS>S\җ'qST"oxy{m׮]'?Iώ;>>#Gx|QŽZTj-܂}bz__OōlЇ\6 苾>яfy{p8pFGGs>s': ;sJç0??a?c?fߏSѷy.Nj}ӧOW}Wٞ={\6~?85JpgeœjivW z0M!Pf+"Pm0fg&8'S;:XN{ܾ .`]= ǜ y߽/{ˌ3~o]<^acjղ/-w+߿׼5>Z4b̲ŬOToors +OºfGG_ ~c5ώ]VZ l>'8Q-M/l ފ/Qw!s'ov#ɻ>^0wa|_Ow!a n|oO/3P~h˹DE^.I_9rcvL}}={w YWO6ql mV 娔ųx*l{ + {}Q09_kɟNNؚv%???~GW.Ν;;;լ_o~5kKVX!,"'[.0ߩ$?3?ㄍcZri;|}~nZd8zM7z^nXIc/I j߉Yggʚ4:% YٯHBqW@7%h~J|zV۳tz@l*ڂ1v ڡQ5{X, ,eA 뾮?`^7Y7-˥TbZi1Kf0{%6x^WoKJ?{);3%\_bEkK#׆KJy+?J~~*+ N Y69 4 )qy@*Sr{H+KVH2 1%h,y44޿g$4zh~|, X d-˞ϨmIr 6SFO~}5>f%0.X/URD//T"ӓf'9jrscfWz )Jn+( Sɭ)ĝ*N&_qƻw3jwWx9TW󱨺u( TO=ɝDw%l V{@ee/ُ78jd§SF@0j]̻{M㻚O:]Qwjt,Y{brsfZ~pќ2> jPkMPS>YIWfߧcD \Mq3hxQM}>S"Z9X_4~vbSIٳgv2T;Sk9*ڈ {.*Nλq+UWK()R{qjB$hľQMK;n)I| Wp N? pgt@IGIIu1br|e9N[j!~V|SuI>X:L[1V.Gv.a)v/+[IRvS)nwk uث> v4iݱ,IƔ/f-uvv{n9%7ZOg(W^NHCs;9Nt)hNqϻw|>ͯˍ%{OT˙b;tȲ$r//22.7]>Y+y_s ?>9vp\[;1{?7˳ڪhK3johF Aˀv|h.lI487Mpk9s( _Bc~G!UVZ`*GkڲȎ2_n"de]%(7_P $_y])/_jHthSlY_ݫw8*󱏜ʎ477- NNnOs?6iOVc~RAZ`;L_jmf=mpyDwi kC8}Zc7 ;uZ^ 1vYw+}E %w)|wDž |TOR{[n:|Du|8Op4'nո0otu#cJ\CV0W~TVo(R0LeîU$?BbC2ȇe$ia],>4(/#՜r#!E?gŦҋڇt\Ue4凌msz.iV#mzI]+~{v+/67拘|ciict~Nz| rs:3~~4_ >ɿ>^nNr:/OGj= P.[jŧϖZr+?}Vjoyg8űLӋ-}6\lvC{;t{-2'+?oa_{R -;4uZi{N46Jė5P__;?1wH+VÂ^,F7a5jY&ќw|?}1VIվIv^JSrMrﭔz7q6/G9gzK9͕Q$&q9y8E9G}ԧ`H*c|2`K Q-_ CZr :ՑeNG wi=z/s6Ty{ේgGϗS2ݘEVWqeujZp'C zz}$K/W}z&Q@)Lj \mBi~p_e{s>3b~y^ OOq} 砎Wg:lݘ:Szoܼo7=2<{;dۣ.﫟{""]ԝ{![+_ڿrU8 Z/67r_åXj+{|_RN;C9~o?hθ[|dռ?ܸ:ZD.|/ַk6F{*0 u?8|AJ__qPhABQh K3 /Y9Ж(xRbH% Xs(6u$dqW1AWx~A_UQ<՗ "+@T߭AJWp+:cqPf<cݯ>;:t萷 S,M'0P?L.z);p*o?~ҪZTF6SEjX]^(1/W% aIQ[>P,/87S:y~v>o|jT-LmK-asəIu4>'?õ?̿q165_*~GE#gb`+!x:yVߵBx\9_l ^o{*wֻߵXENJD1Ï*K+Q@&}]縺6ZEF('|]}J¸"cW z'¶R-qob/lCY4Okrچ;NdƮPBblG6N7Q) 449|]kR._7cE/Px;EӾqa303YW 9^֊j骍ޫTX\MGiԛ^NZ2pD^"$w2 mɭ}ʦZ߉~*Х@UZrhγh_:] P-3I.W; Z 8YdgPPjR! B@^?W&%)Mk.mWqqB@z!pzvB@~ B@!PoDꍸB@! PB@! ꍀPW{B@! @j#B@! @7jO! h8"@ @! 7"@F\ !  G@@! F@ވ=! B5H! Bq'B@4? B@z#B@# G B@z# To՞B@!pD$B@!PoDꍸB@! PB@! ꍀPW{B@! @j#B@! @7jO! h8"@ @! 7"@F\ !  G@@! F@ވ=! B5H! Bq'B@4? B@z#B@# G B@z# To՞B@!pD$B@!PoDꍸB@! PB@! ꍀPW{B@! @j#B@! @7jO! h8"@ @! 7"@F\ !  G@@! F@ވ=! B5H! Bq'B@4? B@z#B@# G B@z# To՞B@!pD$B@!PoDꍸB@! PB@! ꍀPW{B@! @j#B@! @7jO! h8"@ @! 7"@F\ !  G@@! F@ވ=! B5H! Bq'B@4? B@z#B@# G B@z# To՞B@!pD$B@!PoDꍸB@! PB@! ꍀPW{B@! @j#B@! @7jO! h8"@ @! 7"@F\ !  G@@! F@ވ=! B5H! Bq'B@4ֆK (J]2˶-/lih’ G&- X)nkjn&͚ZᶶkniVOkmF|K:Yy儀m !@Rł-,.d g $gqH/ ,_\q~"Cr:2y H -xK:JMCHxAXS[[+>mDbwuwY;SGG>i֊t)9! O}}>WJl$ť%y+>33Dg~acAD#qY^*ACB(L$PMM#y]r ¤F!3G?"c Oe("#2!)AԠjeX Ԍ̝ D=}N{{dú:͵I^.B@\s4S HdlnnI,,ϜkxHrZ0uW:i,ւeLT\ę/)Wulx !2=gE=*ffW]V#9  j$$T[P:c4:mm-~跾>F|kr+UU`$L队_p353m6;;cs+DZ%jJ_fdd $\DZ֘ݽSS6>1nđ P)@'`MA>e‘/ǜe <DpN. gR3;5UZn3q-fIPv<<f5rX Whӳeb߁ ޶} y0{xk9BHie1wmQhD儀'"@D[m MF ;w4lxH ~ {e)v"dRJ dU21 H-’`Wyi ݎmւ)%+Z۱4ƪviG"cr)&4_+F8-)"`"VQ{ $[I]*7xJ V)#DQ qхf;@mwLzYLNE@hsUB.p1;yMCCo 03"[i/ |0YCTeH+7AeWZQ7<4no#֣˧aPLC H&"9H!HqI'I$%S= 8"tr$hxX|IN!q M.2F{˒Q 7% ط+ɚܾc2-STX?"@P5 AΜ9gϜwZ j?($mNYte""ihai`toooXO7?nh?`^D>ѫQ {AO9G$?! MOX=G6L(zCw.;xB\K3&# :!Y$0={;;iW% f18ETN )3HM`Jw_ 0}{{z rApz@fW!)_crDy:],`\>-BQdjKpiYI_;D8yʉ)I'CFHZ@iT :FtB`0(U|8)'S/`9E.~O#zOɒ'܆'pچKGidd9 |gși+q/!+OYʤ % %82Gh9|BDXBKYd$6mHC6ЋQuOɓgرƞKv !vZDUO6Mt:)D4Q?XPDQ{CAŎ XQQCBHg7dߙ}lvvcn2;3͙Y̹{=mҤ tXbD@r #Q[ ֮]KӼfRsB&DD bRDitF3U't6a6d坓TZuhշϷYXI%drf~\y?I{^xjl˧I8w@;7 Nu6O9g=G!옾t25k͜90f5Wzl^d?.6nh;~{#"~"PlޢҼmE}}bEIxKBC$ܣFf@~;->q9(mzh]6Umr HQɝ5 *HLpM9o(a 4pvm 5f@zu ab@5ڌǟWY5/8>nıvcr>hǙ)Tq^gFeEWiş~6d8N:M8]1vP;fWծax4NJyPר)\:9.QxqgsaMra Yy̕v촣Y]gwv~mŊPym7h:9&'WJ&e+֮[B$#,Cu{݇~+Pq?9+l$`e6G m̵}6NX(%ͣtq'U|BOmb:J3FuC0OVq:/fpƹ783|Kch]q3vK~\mFӑ|g;oBZ"  P+)LcG qT:1QJՏ<Nx'ݻ :`usgBIU靉jIyq0QΛ2چsd҇/`m U|;]U/smΰ-A\MDh Ib2(IIg@>RSjhw-%svӸ׽S^,nj-1'/m;HKċe(; 2RLx?[ ߻2qt:yZ殱+ofM[sV }|*I V(BsL (KjD YG@ P>~J-N7Ī00ԈYQ @WIE]rT5b+n+Fqt<)IWzQ/s!Ww$ه8}؛Y+'dU_%J YeYyD;ƈm'~;;ϛ7ߕ^z6`F΅fgޚD\G"& Fe";c=z_@{9qX5'ڋ+{t- ٕ&gĊ7ah\ Ua9}V^{;TR*K&-Σ?{V:nJGYp^pl8!iO^]񹃭SnSfWiݽ[59`TY$ #y"1|EDƂ:j(.[($u3xuVqWuS#bޙ`BL͆ A|߫W6+Z|48}H+=᬴B c i2!;Wr/:C6lP/&mj|W4uEQ-\Os5zsk>6Kt)V&ND@r2 .SѷO_ۗXxbi~`&VB-] 9z$KnF _Kل~1mQQ R/f/A(R(DG\19qʤɄ1Vu<ˮ .Q~zvlCظ> F nv!}4muѭ<`ڼ˙,gφn*WzbW &LQC⣼}XvC;PZtt"`g?&czz[p-Bl Tq5Ġ պf/)%7#ic}JNnBԿ?Jf椊\AUkiڢCUnJkXwEK ƙ;t\~?i@e6zs`=zM74le̺^b mՒ觵.ZgWGRȰiS39r7F(O˪?" J@+7_~W:t xА*kСvɒP i2C鷢2<{Cc%UèMxkC%f!5=cey(C{{ AN/Ry6}.>uθ;&g}7*X]:wC&l}0<"rl'f,7(BMJQ}S<P툎Th;PC t`oaڨ>O`jna -$GD]HJ>쳶~M7do|7{k^c/;'\Ojx;0 7_jr͞=Z,K/d{͝;M#CQ.lqڝM ?=yO.ٯ_?'>a[~g%zoVlcTBq8ύk>^]7a'|q '%dG6{iӝlj.Eua 6&' +6`r,ȱz@98ܠb (e5~(w:/|[.-oy+˻T~XrH":~F[gXrᕘg=&CBAaA6j9qŞ]R'T`l^e1JPhѱzh:?C0BqsI';d&ܦLM<^z̭'?IbT/2 %_M<ن>k4QA9蠃afwqGȢɞʯ<^ҏg?k=r!.Gg>yRqcp3f(ؖ {vm4wglZiРAvYga% _}s&MK֭~^{aԧ>Uz=F$j5f66e.BV|o{G;ι3oʾsw_d{U@ IDATYlOgyomv[Ñ[}xpƂkLQY^{뱌 ] hc}5E裏}cA O>۶4nr P3z5|YGf!*A$ 7ƦMdF3ո=q=jBh~ Ekmdi \2AQpWSPG$qnݻ׵[W뎭GA r]s{9ζ݀e'ikV8Aɶ~Sb Ze;f]p8m~_&0_EipTtX6H9#?G{ꩧ9Z>E=}?Xn#ShIy@٨xT|@i0cQG#(e%e %'da U~ JV'ݣ>ׁÇ1<GaEWLk؎Q>"X~.mLXFY(x? n`>h7;Wn8)8 86/~k2MBZe_C <8ůZiFEgc3'?< ~Qgd:(=n٠Ys릥_ăW -h<@&*'GK,86]=Mji9l̹Vljy=yK_WlΣf$$= G-3|@lfK}w͆/hЛf9rZqجõ8'kQXZ׀>+/=8!-grn DYARIDmmg/a<[Ҥ;x?(iJZ=Y.N1b7s6Ӆ[?l!]ve駟kl:ַeӵ<`S8-|-{gGu73PӺx_|l@ly@K -Va|͖iydd~ѪzP߱P>`-eKK-srVΣ_BҺ&|9h@/-Pq?T0Ag5pU騘  QSA+=*ۺi^~ J<-uÂґ+eQ2 BCTU|T*{ɤ9ո)Cs4ٳ鋅}UKl.W?;#.U-( @TDb&K=cRJ0VTwͣ^-`ni奵=aqozn6=YͱC0ea_HZùAdZRdgwgLn\ʴ<2y9P/E>Kq V#o\9)@9Mv4gMVgce#eDV:>o렌 nM&4a|zz+|WwnyGU{ [?wҟ?_"!_6% ntr8eGX46e,M Y(},X$N|o#W<`6wZX\1jr7u91wPZVjGXؖ,~z|ђ͏/--uM<^- p{oYt+ThuiεVFBcKr{~| ev:^ˬ o,Uڈࠢ ,r;TH$F3{^o*c!2NQrX# CJYSAV`&6ͬ4DyN2geXFeD!V2Ɍ1yЪ/D*3mќc|~_ǀmů1*<ꥢFqMMoz3`8C_K͘lJEHZPV:^#8 8ϭ%r٣xY }ڔq&žM#a6.Ӿv#X~d;6>̵xo8[-H)IX==γ0.m[_AtLg-uTd8?`8#H׏JG1 ?@GX6<5֒l{y4)\gS;2sٜk,i2 y}*4ÙiFq? G>V+Yn6woK%͍-ł%PJ<&sڂ# ??04ʃQ` _-7 A!iR򘞇(s8AfTr>2} B ;B$57 8 YJg!i0e(0j Uۣ4ۓ 烊&p+M԰iK6`Iq[{m3g֛P~l(4;CnđV( ˪C!OaoĔQ`LѲǏx|#!s{A5;,#{ef k.7]/8s`>MR.G\QN>Xs0~D#:0F*{Cbjk_kߴu4CScgL63fv:?_um%FM::ؑvlmݶ:!WKtg5p.ټh3[6j[G6^Kױ XqQTi%kmyP-z|~c-|Vp20 '> ~ֳ<5=ȳHcG@vJc[=VاSD@D@D@ڗ[5wv do}vff~~ mrj)" " "[ͧE&Gqط! hpUD@D@D@v!-Ѷ -QD@D@D@D )@y)@! H*c" " " yHCOiE@D@D@ I@ P!C@ PzJ+" " "PHR $@RSZBT&E@D@D@ҊB>6 -" " "6 -" " "6 -" " "6 -" " "6 -" " "6 -" " "^OI IENDB`gridtext/man/figures/README-unnamed-chunk-4-1.png0000644000176200001440000007767514310625360021046 0ustar liggesusersPNG  IHDR@EiCCPkCGColorSpaceGenericRGB8U]hU>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x c8eXIfMM*i@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;@$@$@$u(IHH(   :@Y$@$@$@@ d {|`    ~HHHPֽr>0 $@$@$@YG(^9HHH   #@uL$@$@$@ @(uO& .lقٳgc̙Xz5kΨ]6o?}Eշ|x%)<&  t;cǎEv0`j ͛7֭[?cʕxgoc6l*Tmٗ"@.6& (͛ѻwoԨQz~KhܹhРAay2F>@CɁHH lذUTktҨQ#<8ѰaCEuuZ2 @vXv-uQF.K Fj`ƌׯ_Jc $JDI @|s嗧,~t݊:|I2(2Ic @?sNOI&W^Ai* H ǿ'ZgjTiiH@q1cuGټypI!@H@:x@bAOطpǦ$ (qR?w;n7ͷAPQEEЄ V(^O'HH ; ,~~ *!0,d.~N#tkgS+rP  D Uh~ۈ:6=kE[D"A$06' l#K3YEf~-9AIHrX"&Q@b; B]x `=]{COv[>N,EP¨ذ@Ee V0<SNw}nx%EqgиQ.D(MQT!^' ,$QT,FN:摩sS8AwjրӸq] JDȰH@:G9n4[7I:pi5IRǴ%s6\@xJ$@L ]L8 wVp5E`8,sm+&{uܻoUir|rZIH /LUy7~ft#`7c$ qݼJAY 8EP$vG.˧# | (QbNqs3|o׶).{xᇭH\$ PeWH?iSv<ظ4Q-"c4EPb"@ OIH,G 掱γ5r۱ ̳68:(Y毿Rz-A)aˊNtΊ̇$  v-u/sE?x(?Ux#~Z \ mBp ,ӧcذa J^S&:}]6vy2R-[Xv^>~z|ҥK:ؾ| ֭[Ν%?P!/&1x% X%I2߲y}`]>|겗Z~t˼ "|y{,̟Q~?)FV;X(3~33Guᘶmۚ;̬Y@1քPބvٻZ+J]Xf_u^'|rNA\]I8nᆌ.%2fܹsa:,jcN cVZ,n1 N$.{4c{Wn9<w/K%S:ISAZOBd2wnʹJ_I,TJM?~O?t5o[kR}swETQȿW Pf{n<'(MG'hK݊.3j\F_ Wv7ނ3ุ>`+{_F|P'ˡ_|!;lB!Khժ{ovmynsᤓNV(=»ロVa|gp_9O=}Q;,ꫯƈ#н{w~vnbsifdɒ+WD>}pqǡe˖3fu~cFˁ,ACr-XҥKq=gϞXl.B4k ~8_租~#8 r쮻¡ C@.}^8~~;ر#k/R;I"J+~5V-p-A * ʂ?^zFˌ#[a_~u7$bƈ"z#,~ȿ̍7hz{]G˝wiEتǛZjo3g9#Cwm>wGP߿'"tAFu^#G7~2Q_?07t+maDĉ1[';8#4{ޢED?cFE,@FDӹNW^ySZ'˞%"4m>y̔)S[}NѾ< !PZ|~rfȓCj£.s1|h䎹3ϵT+*ݯ8Ak?رc:K#ZڷoonV{zTEA8vT߾}͞{w*.{* ToN6ͯTѡb/g϶_+nThZg\sYxqTG?ë&ϧ 3m~ S!Eǵ "X?G-XXwscltl;?$,;C=w5&Mu@Q$< ( ~٥ꋟWfcO!=LZjθOXl]\>Rn];_=e3X_./a&M-7^5^dzh"[״iS|عG/~>D9k3c\k:^[9 f#pˍ_l@~>TwL ܯGÀ oUTìLcwc˹*jCQF?bwc<ڑ^}U`5ñ"d[T+,%nP;ԗ)wѱZfŏJϕ?[Xz_Lw}gտHIܞ % #<+?wEO\8 QGr6OSNSJY(ճDTCȊo8 6ĪUb/C|C9֫UBbK~uo~Y[бEѨƲW\AE ࣏>V'u,Qr^ACz_dĊ ؿ %״Zu9[-AF,WZbI/֩բi>Ff!l$ |}GyL"p$9jྱPcލ79;/̀w}>/\)sctJ]2#j)}*Nw=i9 /ĉ atT-zkkjtK-MKt*c]__|UISݵGEY. +:w-bßMݯ=*J {6׹HSfͲu^~,ˍZ9Kwiwgu$p$qn3ݙ? Q]tjed5/n"f<oViT[b\"V$ݻwK<U&:AJ#,#ͣ'e/s%mmwVҬ;_TFzd?>"W&Nh2"Q MwcDXfu?kKQ;4M}g B;b$oh9oL:&>uzi.4,J*ٹyt:f:uG^ltmڴw4e?וr]bZDÇ۱u|b93GdtlӴ;,q=<&J`…FRY}$Fy7Z.\dBkPF&ԥ{>GHd _撡Dh|cDRIHRPl)a__,%@=)$-/~X\??DF*OA$ete{sX,pM0נּyqsBz:~q!;TOQ%0gI Ksp$@$UtklD^v{Y<^0_ES8yNjG⋟:t^{ +5'Sڸ;,5~%-@%p>$@$ ?~V"͍l0'Co& q:2⎹KyI$ ķŏ&k1HiHH,#<ڃf8/ (FZЉx]gRot{4`đLdq.?TAbi"@>ڵy%(?*J;IHT5c͚5 0߉ɭgڣ#̌Yp3kރӪetL#S]v_hY]򒔐qe@# > ̑d2 є I @<MXɁ%jBbŏ6sd+T`8 3sɥ+~Gmy*RT$0?V;,QP6ņ,Ss꩐lK0ِDNH&sn馢BE/zW\m:R7KV_q r%}HQ}VǍ"H"(!L%P|- M@igy0Iݓи?]"kEQӲû6ƛRu 'Dh#A1yrDitŏA>RYBStpZ$@$@ xm'|2ywYsEwdrʛgo[.CL!&k3INRƍ3И"4Иɓ#ȵN2K-Rukq*aPq}ʕ2 TPbhjHhVbq?`sPt9c)z`a@vC;@GG|!IQ>0eJjSVKFN]`ۛ8G$@@`֬Y61NiD@V;x1VTu'A$hepJqďx9_^>|8^y Fr` ؾOaÆ!%$+ȹGgǓģf8hCkux-??ld9 #G"ȧR>)JlHH -鈠7?i зm QZ HsuCn/M֞ϪU@˖ "'Q2? dΊHR"p8<ի>H)ď!6n v#{xbE;JbZAs/{(: ;Rb&b)Yv5L$@$P\ԩS.TJ+Mh ufB]LxƝqo݄*T3/Pwo*ܮ}t;ܼ]Șӈ{=ٚiӦuveb$]`%Kr6$@$j ҢT|Jf$2tC8޹oun=$X Ap Q%1_~96y20dHmrOPn"hp]HHHG0ރsbw h?gm׭޽_}pv޹GkDWx @g̚7O6ODk#u"(QRk%̱H$@$PVC?Ӳ&FmEG'C`g`Y);<.{%< 8Fz][HFw`Z@*U*_DehҤ.[尼L s| (2%4Aoߌ@ sp'xW_ gR$J-}t;` \Q#n 0/΀seO1P4Xi`T EP*RC:;$ 2AA~Mq$\oib:?XANmEN_Ÿ.s{>ȶ}ԓ]8φSvƦgȲZ~Wϛ^#"H:v{Z2mT$"6  0c I qC"Ȭ[w|?PW5Ӵ?0'ÓdF֭6. |]st`wϿF|_2}O Q!>A#F| r10a-ĂDq7F6/ 0kc$￷ k3"\.[C]tB`\I'f20&ٺLgX~|$}F*ϧ$cOILjժT`cyM$@$P 0SNbŊfyqoՄLMjmΝg-[L{/[>h㾲1Z LkGA5k.yk§iBp&7އŵIYgƏ"n p1ogS1wܑMfʕ+S=% HH *t矍8!_L(qy.&9[.>S'Q$b,ظa5^8WDG4՘1卙2%gg5R%c~).#d%֞NExH.id}qH9s0' #ٖ|٩ȶ,UKMdF7;@'s0|ꗕ#To8HDE1"5b>nHbdu9>?)gHHLHG@2I. $p觙;6*Lt/3PGviܟ.G/~Y#l(ůJ׮Ih-nN*7t/ytiI7EPc&Se&" 2F ]kCDsa牧M0}2BQL6ˏ!h1̈́NO=ERuT}a~f=rDP @.@۶esGh<^ODò @_+8}z=sMw&MJ->#y#d'.IUF5{ ek]qM2#ǵհB6c(@t-9Z-AUFvn!L'a+=PlxHH`tDPGԭyKԂ7TP4m{oO_8T7dMNQ#X*~{ HNZpڷytnbdRkOӦu/|[dh}ի3-i.ۙK`3$@$R]S"o|u$_X2ONE-Gsh8O{NZl5{9_߸`4bI#ׁˁ74ڷ9`R֧[.Ay7HH@"H8b8nI8Z?~R`O)ޟ9aJ$@$tfp*s?F<%U1Tl ŏ40S@mtN)~?QP!w~ZT_'Wb%Q+n{MdE^PITH&AC AXʅ"(9t@bk  \|ԫW/̟?p@E~%̪-?Eŏ{lg/bD/;/fljw@^i 2o.IY;Lfג[DJb lI$@$@P4I*P>YvQ#;g^q5{BncG->-`$=Q׭KݢE3s,?ݘᇱ#|"AqKU7+ɻ  O@vڵk+V Zo&j UnE7a9T pqoB0 Ռg Xp}o4+t q \hL/ u9Tc*V4fhԭ[׬\2NYԒlW|~  ~l 8 fqӀC1 ^_hS\x\wKcR_ۘ@nC`-Θ'vR͚E~!2*jٸ@rB'ZH~xe㣒 l'X<"U;S*By> _| 5njm*j{RO+nݺMbe]Rv}[i!=WΈ̛he8p1?hG/%u@KP$ d@:"ȟMkMh ٪X4؄7-SO $%\{ FL$@$(D_wL㎻x˖'tˌֲ?\gT iq,ʬT#ꠝs5><IR $x) E ̍3)~hB:o*; Onw2_;# T ?f̫s9$!~rMdJÎ$@$@(nIg叹>H3P:v{n->ղvm[~n=~=nO,EP' H@qOHGfҥ>TøoziBSބkk4u嬳rr?6f]"ׯ.;VD v$iTєm۶qdOiذYI=q#LjmM;[ѝaͷ'?v٬z] [/9,+cv-"~j0FddW B S"(s[dR82]v5*Ub3ϴB&{Fx}lswTm㾲 :\[ {Ŋ hQΖ-˯6lA@XK$@$+?|flײfsPk_IŸlt<θ=zHcy^n-~Iz-Kw'~lA@/ؒHH"KيA's=zaÆ7|3?_ XAA!vKN/ unB{04("(sO~EL}NԧVSY*ƥ"P)~y: %*o*"$LW^G<3dȐgwx뵫ʯJSOETgW}!50,{5d[R>$'HtI%Pc3ue}YA@L P鈠؉K++jy„ G1{kժϢ51 => 4ȄjMf}N},n$1ցZ-CK u.U'Uߥ MHHDHW+)2v*jN}&k8jժo6ƍեTUi-B, o?5P!bMJĊ캫1Z{YЉf=6(l6wm~hMa3ƗhW\m<@b'}t[5V\BŏӝcnHG3K ;eڵ IT.0n~2%"pTht܁O=5rKz'WWDPrIH3TEFIX:\RϏZ{j׮m ^Y~iҤjܸ[C6w-Gv uxbo1Kߍɘf͌z31K`*6>$(&/ v!}'C;®hDDj*$66ȔhOO4s/ RKQMj&_ga>|}~T"VoХ^t0FsyglthcLD.O?kjDߢm9PQgW#a d9`c$(vʥ, _?; lo鈠; /=*~rGϿв<[T,j74K.NXw?jn#q Ka0O*|yRk\EP@^: ĉqG`ŊIY}CXuX`t-[@OU" ! lS﮻#8g:㛪nw~h.O? +0r${?l \}ݠA?p32p@>ّOHHHHٕGj&ՉZ^۟M9s$E*`1:N#Hż74X|+e%ȵט*UYM%uڶMsCx\Z-AYHHHHU@57iӦjժE䩾zrw)}y@id$b(KUpSZ[=4"d\Q7#fιg`cj4fE9 sFD%HHHGNUBgO1-I} )*VZF'Zym 705~ScȧM*嵸 P-V =_^}|k_h{L!I&(zlL$@$P# zKFstIm"% /`ҍ7[]䱊(%/טP6vK;:v1vg[QkZD2b>/;3s1XWQ-~$eZJ4 ^1; $*Ggy:ۚ=V7c,\q>Z^_ԥ~?Z3}n=m7(d_#x|~L;a%^J<1'{ ;b#oA_pa[*t |&XUN2EbW1^LAPێvV%3f~mE֭#2Z2یBʼﭖw~U6x ~&M‘G{/ZdUϽ]=f7xC*Xt Z7dulg`֮>stc+![![ց+8,`ʔHkn)r?".npUWbWSw d@"H,7HU{\XLD5ʶSh"@٨SכvfߤɳrdQGec }Ҹs_My_m4fwXd瞛;r)ѱS%m6m;t};~g՟4䮻tbZwE΀pֶ<@āoSԧD?PTD0NHH@Q *bV٢޾}<mwuE-=j)zż8'pm#* /0P:}X֥2\t[Zw;,g`O㏤ˈZb>Vb^'B$@$P AJ} >}M*V 3k,ӤI[ߨQ8eETE.3uֵӹE⬬mTDR{x⠭E 3fMh6FxTna~1Kɘ<97H'eI`TyL#$@$@ە@a"hݺu]vV`z[bmӣG+H|~"H)j;.J v UmjOJ!VZ Sl PvgX*]`={Ӽ1$sN_h +*i·HHX &lbƍgڶm&h;IDATkSkt?yԹsg+l ? u]Hv g>6#Լ] 4Ȅ7sl{~bÿK/d9gR(/G' ( ANk׮VZ*KvZ_(>l$W8Ny^+[1ⶓֲsmNꟖOo% @vHdwXQDZid) T#B/^5jԀdǚ5k yŢ'{߶ӥ q; f{wDe{|:;jU`n/̆O+2Xz5 <ۣGc lGX$ߘʔ Qږ=v%Hcb${lj50yhwk rʛPZ{-[)G3ɻ/ UGj0F|.T\4)jI-嶇=HHH$Igs?!#4nik?+ GVMzq%)W5Q" F|O;ya:Yb'ry'aiHUc"iRԒZ$Ç;  ^gy&ΝRb7|3$ KP|~pVeR9M\_"E>n#a U^=ZdM&PEKC=$%| -@%pZ$@$@ۗf.2dHJ"HHkyb.?83qaO!a&O3 8rFOI~{xG8~lYؔRVď>$PʯIHtDP2,y_~ O!x>CIfh[o̝&zw Lʑg6S'gf鞔6K[g  2E`{ Os'"P#0zg睁:uڵ|ocY2o G#Ekl/G6u{[ʨؑHHJ.Mn:ydqPL5}?PѲmz: t "пosyď{d*v g)ߺ}` _;; U%Txe/ky1ZbȘRJ\[7'HDR >)GZjii?@g" 2Ayď,{3JQ9Mt\iܡ#gC8C,&.~Yg[  #) W-?==$'>W\j |Yٓ6:߭}VZ×hJk$@$@e@&,Aދ3`>Zg 9 igJrxjs\H]^};8!?1d)b`HH#;`i+'@vb3)-u7s~HLTOi0ʊ& ?IHH gpF ~=4hwpK3{!GwK~y!3IR`Dc"K F)kGQ(/ dt}Gmө#E3Wކh +L'=:i.1G%XʢG(/ Hd 6l-OVXf<oP Fŏ]=:>~ں TVŏJk$@$@$!rj׆3X"x̆ v@ RU|b3 NEIa?~"n{|aÖeMT5  (@:"(0~m[­&ZnEj $INX vʕ+[o*2?ݺu+Y |".@L$@$@H'Hc{0MWoʣ Cp0kHҁo7Eas,ڵkѤIf;,3:&;`)'[ @i![zsبE;X1$;`>X ,C I`'i+]  686;CH[u]( b5k +K\+KoB$@$C:u* <34:Aa  H@OPJ[Je[(~rR  tD3l8#ΆYIbqR)?Ըσg$@$@$Pl^[ "<,yREE P^&!  b!%H'T ԮaÄG?*ZZ  (6XpFS0& tDP") ë$@$@$P,]+lR?щ\*[ @(DثJ[ @AK,I?PؒHHqqwAǎGh׮z!8dg)PhxHH/.#GK/#<2hР>s \g' (QFŋC?/"I?4iZn1cP*zZ*  ؑ6mڄ?x'qaW^h&Jč7o =hӦ ƎoGNݛԽ2NHH [5k.\k⧟~BժUѸqctD&MGF(89 @i @8G   (NF$@$@$PP9 dPFqr0   @4%ΑHHH (2 @-q$@$@$@%@QHHH4* os$  ( `$@$@$@PixK# @F Pe'#  ( (J[IHH2J 'SOŵ^ 2 NR:^v%K #X?`w.3[ YeVK8ꨣ^\1d=:'%@ŊQz99ΊHHT(Wg_T\7oRC=_dX{{L? \C,tr*mc1c~-g`]Gd`4A$@$@ۏvvoi0_Ѓ &U**~Ҭ$sS2ų;ܹ#Z8ujP -HHJ.m{NBrpZ57K>'o<ͼx2wTaϽ'ikW#pkg{}yvZ=w.{"9㽺fgpz] E^[ppkuAQ{/z f5;p!K>q/=z٬Ѳ=ގ>xWB$@$@;P B` ]̚ ξMa 3sz fOfӟp;GХ{4)Ż1x 4+;h?WE(]ԭ2.mbzs^w8{7As ̧_6fT@a ;icx]_9;_"m.{m"XHHH`GX ԝ.m?fRY?_Fum3m3SK0/gҙG0 N'KI5"3a$w#:f? vx)>#֦1=ԫԣ2Dp)gO]2Q"8hq?<ůIJwg@_7 m 82E[ޕw!8N.Y`pZ4;.PB$@$@;PuI9h8;@&ul)2k 4*v򔏾Dӯ{Th@h# "bK'+,g_{ ^/uj&<H:Ʉ%̻`GVn:}/_U-@-#(zS v"%@>*M'+\ŪU0'pt76!Z,¶U'eL}FuXݪ?Eljspi':?31y[Qs0̈́'>旍HLwWijOZex"P~8NC~-2ii& efG,x']+\6/  Ϥ̗k|OP |?;U2/۾Fi۸o ;o#t>SPƑk.v#{wޔHGkN6úyx+g6:Nã>{U6۴„/:ۄ䙢cլkGۺcGP1+0ѹ{9?m`_k @"(6$@$@$@ePӼdr_J$@$@$#PAt%Ncߊ>}=Rэs vnm;v$@$@$~mu ),er~d/} i=tUIn2:n& uyDjx  (E(e97Dp4ͳ%@J}ǚ*ErT ,SDx C 6%/ƚ_p1w<_|Eqޞc`@)q dfwyFplgߦ0?\= NV^ #Yb$y8R.ufٮDx"Rbwc.S2K ɣ]9މF8.Pluk!pK{#5ۼ'ݝ ]ӯvb' ;icx]~V<+8uBӣ9>oCppn|aB$@Eয়~M-[Gߊ>G˯ǝ WAЮXzuqM`r.m?fRY?s4 m i q/I6%r߀m#VBmA]}7o=n7#l8kSoRx}FBMczW ΩGetS2G-϶˻d Epu:~p[y98_@΀H1nAXUV P%fuI9h8;=a:?z9ˬ65K?DШS>>:NQm;DFDDŖGOlW.}^W0 o0 ގ%̻`GVn7qu7^x/.Z CZFQDxF$@O~'ˮ-tǿl*RټH{ե@&6z)̵(VԬ g=)NF;D'O/uKJk"l\yyipzt_[= NNhwG BynFyE[[/v!i_m-gfsȒӿg\[ @:?} 8ɎO D?!iT'8W %rdtIK|+wV9\- 9UVpOȶ}%v^Pη?:oɗy}*Gz.Zf P H#HȞfN%le1`&G:Ԣ"1;hNf0[lkHlg)ڻh G_ض)Zcs\oq5D9\: ĿAwCj^ !:NGNe114㒽?{7^}k}~~yѡ@S9?j;,IΕAsOf=IV}}:'HiY2歀.>ƱHjuCH̬YW cO}*џŮLiM+j[޶f RŘzxM]+f[w0+ I {AN0ØHbGtʕ*b\B57B'B"VQmh5˻=]h#=j tIB%¡֡܋*̼7\h>?L2Պ+3Z"AZI#xc?,GEA$'=Ehuv!))_1D飧@E)[euk_S̤߇>LS\gl_i=9s;IvJ(VKPei r%@f#IC,$@$@ 䚥 0J~rS %JGi<( KEPdq@Vֲ4d06+h.kLiKCE c`{ @"&ZRHD's[rf䥠+||Z.s4E4 (񣊦V ]lO$;?/'ef$d1KT*wr0$@$?>JiO6w`M?A#@GHb?>wi~O %GB$@QObL .r4$@$?^٢Ak}ϧ Dps𷇻7t- 0%h%'' 8P1MaMIH "96l߰sFu"(W|y( y{>~f n}r< }'W5^03XHHVeoNإcTԾ1~]iY Nn_ o^ݜOB9@s'egdnQ?TN//oj\pK׸wN9+ZXs%x @GEu}A+2ww;7Des|#׍a@SihԬ9Vҥ3; k`H|U ֥pMjO"-W(\NPŹ`+JA+!Pr{_0.b] J \l]ծ?Y~\6.]Oߝ( @sYKok%#B=JMx0~ûGI4 o|wZ9r4ggxK77%mQOPz o#KU|^}&ivx=|9ax O6cީ}P?uL xzC:An-O^]g $`Q6m/ܱA*9ؑCXBOwtXh%> P#Wp=~ dރ9,hCygp=(?" 9W 9ۢ$R5 h5B_8PWW<9^̐sA>Zri/ kD(=T( Sb_MgTNRp-I0~ *hu= ~.fp y[b,c'BE״vD @VVe8۹s|WT)VGv{5h D+֛I2OpU`,ބ&YƧ&Sg诼m B͛7Gd7o?Ných2/7AkTթ B{FPĔQ׳OCz{K{Zm ͜wk?RiКH%a|1>2ճ5k:E5;jݯ^Pd! +ŏU(+r|~&yPz?3Zވ=)q}[|%v|$@T))?BCާjٍ~kص_ش AN}Odd\_[$wHmL.eGEcTΏ4'>e$Ds*+Hl$F+ZĬ"Rړ-5Дi>˲@Zv7Dh0)ShŋeM+j[sY^ P:4\ƼwkKR9E]ЮkO $`|)ݿjL ~^j,GknXOD%jT5w3g{F{@蒄KCM܋*̼7\ꍖ)&)LdJ;ֶ9R<*Z&3\Iri2xCVx_1}4SHV,T/[;Ze&E>oeR%>dfcJ/)ϙ썏Bv^Yaf\X:. Rl 5MA|0 ʏ p!I[ p1ďԙt,6~B@?6Bb*A}*͉XKԨ^ʶkF %JGi<( KEPdq@Vֲ4tSSm*T]v Nʗ=}L{1=P ^@djR>պ^i"`"UZK~uԳpϾ]e葕f%~Tє*ᾋIMJs*8J1Y<~ WmATDBc1Kli|= +ŏb6} $ \ jIm`ʥ#%7Ht] @Z(bjUO{v"(Nд}'x@)~,c@q;B$@IH]^ ɎJ h~)~{R?t@GRԯl(W3yP{r1xH:v_d#dJ'~NOI=緍 ܰ{Yv+o 8G? O8"mL[~=Zh!Uq]Վ"ݑ ٠xj:t"E27s$?:Kt]7|ds O"YL|l2;C <|Uݶ=U*WBBB"Uc`H \ ;sEYJn<.+OBB$@&`9{,JF439@d. @TC(@[Veg{#?QXP + G s`g9hph W@v[vMcʈ2DQbgIHy?g32K ,=F ^D Q(~6)"O% 8@z:Tg]f-0ў-feO䢣Ï \tىޭ[05(W8FƯL%J'Ps&IEMFFƕ O*zZz ٿ'c^Q,ړv,6? uE'\՞'GC$@R:|jp5[!9I|̙&`sA بK~(IH ,v:ڠ1dx=3'7VP?Dxat5 @.Q')Z?!f@1dHB%p)϶k{oCBَ',|QߘIQbHB#p)a}MۢfB븴 ]4dP̸% J~r=~2Ne|)GzPJU`PK,63~IH OVuжMp~}J$> 0X' m!@d V% $`Qutj#k߆ZˑFA&AG{98 (<ŏ}FM {zV]_+ʔ=1er7K$@Ki/u{R#II5Nx'Dp1ތ(8| [55u0_NP)~%)b9 X񣦲OMDƵ0Ͻ8|)nl)~ԴW\rPa*'hΘ+CPbp|^dxH@JJ Zn&{GU+b븫eH(-"߶ΜAųQ)s)$(V˓#@y ^CJ?=OZta3a|AU'_d|xP]| 8/5k^ Næ(]*%:2Ϟ>-ȏTc&Pl&aCţl*r]Z Ξkp[H5t(vƦ] ;GM$RSSrJL:/U ?VK5 ]EϬBc%MR({@$@1D`ƍ{Sc(!cU9{:wwBFZeoǏ_,$@d!L" HKCH-؅Y4rh(ˇOc9R@ $@$CV9+ܲw6ώ (_t@9_H$@@&Mqhΐ:q=<ϣ--QF-FC_#F~gV"m @(RFO W.ŨX:H櫠S'Z* Pɱ D(Cy?~ݠ)lRZw/Az k k&z?C ( a D uWbxa'A SSat}X'5|q  @44 o͚Fblϻp&==a]羣^Q)?cP0 5! (!N$(iDc7hJ@VҤ- B (e!QE39 @".OЮUdI! !Đ*sBJUrzܙXx|n-W9o\N$@$@C@%F4j F㷳g{=(u5˰qgu=N$@HJJBǎpt eJѐ!C0vX 㘌z$P8*'"  0m4xaHH "|8w\ݻwg p" 'z}"  nw:u *Uv$D@N D$@@`zL4 U\acc. XI CGzz:zꅶmZiH6@a   S=~ 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ϰ_$@$@$@ - 8S=~ F64L$@$@$T1*IENDB`gridtext/man/figures/README-unnamed-chunk-5-1.png0000644000176200001440000004177714310625361021042 0ustar liggesusersPNG  IHDRz4iCCPkCGColorSpaceGenericRGB8U]hU>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x c8eXIfMM*i_?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.Rd0000644000176200001440000001034514310622556016020 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/DESCRIPTION0000644000176200001440000000272514311055377013626 0ustar liggesusersPackage: gridtext Type: Package Title: Improved Text Rendering Support for 'Grid' Graphics Version: 0.1.5 Authors@R: c( person( given = "Claus O.", family = "Wilke", role = c("aut"), email = "wilke@austin.utexas.edu", comment = c(ORCID = "0000-0002-7470-9261") ), person( given = "Brenton M.", family = "Wiernik", role = c("aut", "cre"), email = "brenton@wiernik.org", comment = c(ORCID = "0000-0001-9560-6336", Twitter = "@bmwiernik") ) ) 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: curl, grid, grDevices, markdown, rlang, Rcpp, png, jpeg, stringr, xml2 Suggests: covr, knitr, rmarkdown, testthat, vdiffr LinkingTo: Rcpp Encoding: UTF-8 RoxygenNote: 7.1.1 SystemRequirements: C++11 NeedsCompilation: yes Packaged: 2022-09-15 13:49:42 UTC; brentonw Author: Claus O. Wilke [aut] (), Brenton M. Wiernik [aut, cre] (, @bmwiernik) Maintainer: Brenton M. Wiernik Repository: CRAN Date/Publication: 2022-09-16 11:16:15 UTC gridtext/tests/0000755000176200001440000000000014310622556013252 5ustar liggesusersgridtext/tests/testthat/0000755000176200001440000000000014311055377015114 5ustar liggesusersgridtext/tests/testthat/test-null-box.R0000644000176200001440000000222714310622556017755 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.R0000644000176200001440000002363514310622556020647 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.R0000644000176200001440000000025214310622556017771 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.R0000644000176200001440000001531514310622556017742 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.R0000644000176200001440000001612214310622556020775 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.R0000644000176200001440000000676114310622556017202 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/test-grid-constructors.R0000644000176200001440000001227214310622556021711 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.R0000644000176200001440000000307514310622556020626 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-get_file.R0000644000176200001440000000065514310622556017776 0ustar liggesuserstest_that("get_file works", { # skip test on cran because the url could be broken in the future skip_on_cran() # get_file returns raw data if it's an url and a character path if it's # a local path. That's why we test it with the function read_image that calls it expect_identical( read_image("https://upload.wikimedia.org/wikipedia/commons/6/62/Biedronka.drs.png"), read_image("../figs/test_image.png") ) }) gridtext/tests/testthat/test-raster-box.R0000644000176200001440000001236114310622556020303 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.R0000644000176200001440000000721114310622556020744 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/0000755000176200001440000000000014310622556014202 5ustar liggesusersgridtext/tests/figs/test_image.png0000644000176200001440000045271714310622556017051 0ustar liggesusersPNG  IHDRu,UIDATx^Li%[y'Vu5nHd&(AC_A2 dM7Fzdv͛y"9޻wu1GX{[_}\ѻks cξ/cHnt݇K!PVo)Zsk\>^k}Tx?۱9t5gqιZkJk-z>~[TN1%5*qV]c:>FB?{ō]\qt~h;#w .fg곮݊~P^>;Ӈ'q.9B.u9?K )ǒg΅Fikӧ)<έ#1r9vkbczǽ~e}|L15n˻}n>1}Z12[?Yդ3Ys #9;vsCpι֎>>\cM>Gw۵rRܹ:mhlOCp1}3bQ߮Ft\UǜvJ)1~qOvaf}Fnm-wE2z<_sܙϬ#,hlQp)6y%2vcM݌i 7\+c})FNaq1Q:;+sEOCM~'?RѻI(i>cd~$_XQ3ډG߻h>Xqǒvs1cvK1p{is}>&܈ a1y!v>c\ 1:Y{=>qTܞۍ ԹNM=wB\vTlff_#8yǸ{~J PҚѻFysXs$b8ۯƎ.tŅ؄w :O}c4 )}.K>jH)9V 0- 圷sS$,B&SL5sDwIsZ<6:j՗ꩆz+:s![=!ʯ\p 8oGЩ_?@p;k9Nxc99>^sKaZZYs}ȫq~|Ҟ>g:·]|s6*)c@혂[a>} ͱcؽmYV_bLs6XO>,Xy/ Tq"Y|h(rɅ8V ;Yܫr@qlm}wr9^`0fIO uB#E)2s-b Q[5;U?cx?rkH)fSe)- Z_Q L5cwiy[\29I=:'B1"ŖRiUj\fZ1]pp‚:Á,s~ű".cx|8;+S>.7r̜ʹCQcFYى ᣄp;o?rc$TKs\YVJaTcC+-XË݈ӡ#̋MJSؾʥFּ8\9\;G.9n 9wicYkϘ? F[Ź'uDK:\+l7,quy,;ܣxmp`B+b4BQ7׸c)_91b 6BKk>3/}&ƴ8}i8,!91Pa{`Zc 9Dй p+݂w%u#`5-5!D [܂ 0M1ArJ<4bs9DcqS̖K tǜ&`Ccd{;i]lbpI _XH je PhӴE¢$(,i\D {֑^ns3]nϵI"Y7>JȞȊ;]gvrB30^sZr ZH_軇r%5{;3 l.p#am{~zC]!GƜ[,XA%T;,pCg\)1DT9vR~1Dmi̐l! _> ~7*[Y rjY{}l=$A ɘgX͈4ޥ֯Lmx`3ESV9!%6`)0ھt2ͦ^\z B8'#"Ӆ#~Bc.Ԍi(W4-z99o"ِJ֌d*$3YF29"sl1ūy]$ F)sc9xYP9\H3˩>^pR,ijF1xJ1Zϔb i_kac$D] 2U\]v_^1XDNaXk1{zpŚ)х1/Җxsm!BfK6OvWvOȼbڢ6\P_Gg>ȁ1w2%"F6!E?Pfsj:MSqUws;iy漘bHsx|[9 `"~S!֮RcVmv# .][71"]בVK|7rfR|qkSJ*1f%q%o6-'lZVT:@zNq$f B_{B_1z~r-~0#0Ea+EVѧ$f Q>#az7Ylbgd*S__ N ~nCQ$0Ǟ0s:O2`X[{w6F[c?X vEr1nxrI%s-s߯7)9$fAbAC=0&gg-*K q{"H !?GA)&}LuQU[m#E>e3>,Q~qiyqJIsd<5T1^"neiYk>p2v\.̉!;#'oKU1z\DcÊ)fv3D-D2U1Ƃ]kȄxOawk>K8>3 Bޯ|I X!1u|Dj|{00[qpJFG2x&ii~G^CZӽ>/ZHak{$P{2t韃M"@)%P: BÝŊR$Cڟ|Ȓ;8v m1y:GǖSkZœaHzѯ(UGBΩ_M~Xsǭ'z/RI#øLw.GR(]Q6Gxoā`Xq>z>p*,mvyͨW2Ij)~Me%]v!AywOff$!LC{2l[ )0)Ӽfλ>!l>zm¼0;NQQ>;ՂT,&kN b8SHҖ5 >ux1HXTJTGL*ЄntԣQK,%5єZ.jt)kW>(7 ^3.vG"-0WCRYR## @-j̎ޞVg(1+>=6ޖ۩)XLO.3;nJ0k^!OʘPPJwG|kĚU&(1orEpB+J+M"7vjb2xP jQvM?@u,#|\RSn c8Ɗ }9wLkwT*N vş` ôWbf'eCH#Ah3xN EuuR- fi h9q3HܗĄ ' 䗚ǘv%I}hB웢Գw;ɶ bl6? c5NrDW| Epz RޯwJɥ 1Z FdV]wE'\GKy%kVZy#ByQ,pt,&(_22j 1ZU GB yg2?!S!}kxL SP񹮹/qPa(Z3YX8rbBqlI焓n p iYI8q0nf;z,GNZ3Fi nRwwuNq]>'Vh=MQ=&8ȱ DR_ԆyEШAqi<ɻD e^ŏ s9Puns*)b"~BGpe9]l> jad&ՂO_QJ@Gjy`x?@T\X.S;~JʿQ- IRFE *t8A3\NPY` RZ\1hޒ]!@a1ixn`-SsRaZ0UhmӔna1^`/l1x#?=BzE+>,B[p598zf,r>9jc|j, QJ}CD> #A#9ƕ}&W K$/آ +1W O="sV]m#~rb*f_ϞA wqJaDJK>yKZo£3{Lрu!%ιx+4Ks͕I5lmN(o^r/#1$DqM=xi&}]s:~K$<Ď 6Ү#4QgϞeq,I|}Bh! f](Ǿ3/.;d 'qyM(gˋR Nkzn6,ϰL T.QŤE5 $b~Ueq{791*N8_ TF `"'R>䐪,}e\JgHᖣ:u]o Nd J@aR726;kvr#9g , 5z:"aJZӿɸP")f-9wCR)v!9ŀVlHPPKmϚn9YRD{=p/Sa1AIL(YpCa{2QꂚvlQ!$b&ʲĥNF9$TpΑ2shcm@ 9ډ1Fy֐v$wbi+hb}$Ւ_z$ *hNYCqQZ9lfQagɕ"K:6 nm*48ZRo먏9BT*Li5ȀXP$Ok'p$En[갞_~;I 9`JG.%{ 6!QJ5{| bn3y!>錌/#hm}$G JV;fMq Nٮ` EJy2sk_ ,LTć0^đW`f 0H?3(cbkl#rK0*,Ҳ Kx8sʉ#gk@ Hi.bb@[nYiE,da~qʼ3;"3g- QfOh: {772"k0wTJwUCy?sbݨ:xOj]Q#+=H1f3"S1[}Z,dD2JrFA"n"h0!{谑`@$&VLG-1MQ8"g;wG%"ս f&[ 9kya[Ga^,dI_=(٤qYPZ8Q@b}.7A˜} ̽fq(*>BzedeSZCV>mifQ! B 4*Jn c!ot$ʶ\ihh|]{%lWsaY$glvm<3, gX&>GBnF_&J"-b, gjF8c%~{ 2al'(LB=Z*f !IP7 o'DnW`4A"̠T!QXȨ_XԂ4S_.1{`æs}_][Ud]8(W]0#Z>ygmؒ0آ1mptO "jM O%ZEeō9h(n\Vvt;219obrGD휮9νOĂlj9[)>ϰldQ,BKuU#kLEDEgܟ#q;vA!Nb^ɨ`g%ݏkJEi&tP@R6ֆAl`TSN>P$'G pD|dǚ^X 5r$WBMZ d̓\r-=JջD[-<%G799[il:8!VH!?v J.6| c#!\ Qf V! WwD3׆B\/оC)—->09h2ƽ]PXM!GhRe'X Ꜳ,2GE^/8ۯ>%Haf1sX=gِ7MLK 1q<PeOe+\s2y<7A UM׈ am -zI$z|lw^@TQG7ǒf&\0YPzw* UUf9S8ʤ66hP$`*\)I%zoڰ{txEy#4< 'I=(0ڮ9i eSB/pH[IF ,/_H]ٻv,?dopH!4fr_f,LB4ɖSv50[xcpT,/s;edD"=m 6YZL?N9,0\xo3>߻ה_cLi;R5Y@|Bp.jMtڜ*L vXB\JFrh 웵JX*˜ҫ]j^(+v=8ϹRNnDDJ")><RpKj0b$^șuݥ1cbօ}a#Pע7nw5[35Sa1"|_?~җT(iXkqiȩk1]]aÅ0ĢJtQNb4񧦴I3:OoZo)ZEtҾG+dz Q d> nQh|4Z"bV%S)$0vhZZ ]' qӧw#  ZIA?5ͨѦ9b)-f%ڧ>=[˹N*7ē?Z5{_KD%Ö J'әLr$h"]}LvN!X:ot:GBFպZ S0!uxf1Rׁ-H뺏ά8U#=fm$Uc24002pOWu"+.<qd8Nr~ۼzHK *Xz;pxd=s8˜=׃kY,v%Cx֮ƣbP險XE4>J Ű޿^Z2 `fd Fdࡶ~,de$ #egH_:q Q?<>ggJB팹Gk*}u4;ѣ1/](K\uCcv2V&HN^ q5TTŜCZ*Im6PTՒͮ(K8o=nr*lTbi;n:rQ3^(OT/Im(Sasxy&(j+ox_]ȱ9 N 146J:G?L9 rFb#3OOCk9 e#0[MƠ}qIe0+ÄC5hb& @0$ 16;*z>B,ؒDžL~n⯸x~:8>seD\WrYyA0s}4ę% قriUy''0(C&YTI$pvA#@WDha_A7lp+A4Y.)F Д2O?WY7 S㮞K^b G*q>rQHkBJ}!ɉGģMNh?ӭ! ]6 WEV^gG]j>7g5S{r١;S9;Y# |&BuAGUd, ֐} e~,S=5B>˒KRM3(E[V&rI ~Y 4adrBRwX\ƑQ5>ُEQYhm2'tB.!Tn{e?S+ Fː0Wc0ԧ:JJH)ȨX,.nTIߔIs9h:P"dB`|҂TWͶ`θ]qυb\\na" G~`5XjB07=e9&F fU<6wr'Vu<@)뢣%i -S/}ݗ9pn#b2e)>Xv )n?^P`%\s}䯥|ȏ(RJG+' Ac.>{7{a _dN 8 Q9ln 5+'FZ(GSn>uuE? L.h Kք8!H29HwÐl;0EnbIщ}kv֞U><}4Ӿ/Q1y=]INƌ6\wFVi7bVTCPRKYE%?Y9)c8_BYcK1KZ G83IV䰒VA*NB!c8Ƶ8S:k3()u$̆^1I7p)xn^T~zǴ5ɫsLIh,̨pD/M/Ws|ԇ/%Ekms)wtDz> gD8߉XiF 4*WlȾKJJc)tl5ROb̦aSz(*ՄDi'raLpx ( bPq #+.{5@Ea4F{ˡ#>^HsubT84GU&wJzM̏x<LJP^WV6d=7qb ϑ).v oa!7 :UBWmnw@%!ΜKʖ7p$ iV {ϊ lYAka$%TBC} k_$/Uġ%)"%¾(¡}̀M#rQ a! eV]7pmlY/ZtZ~%*OL00k75Bva( }q–L P#g]wknMqEIuux>z0$o(@ Dis.E%ˏJW8J(~k~*_qzdv"8,hWC W{'rG$ ˣ*RL-FБ* Nc)/?IG)y # q}PSF;(7PGX%X wI\8c)AA!uɱqj,.$>3ԘCy~nzQ8Cڴ0nAIp s#ALKT,aXs$pOstb{˭yZI$3&#ZJm];)ƻt̶iP{N!deOlL,hkɋ(YXeC]1oMQޜ̝xovE=C D϶ !l7Qb!V32Hئ>KT/3gN!5a4+dȻ̸u&އ`df`Aνw1?h+֬!L$E%(`lBSڣ!-@3פ {(i5VFqhE$1oK"<O,<(Dx.~]Gb8cG[WhPP"XCW>.t6 E{&gmh#RqzvwQ*N9.i&@]e-¬1od 6+ufvGc*?w>y#3VU;8 CR&p06ڳlA6H|1 (*T ]: )ɜ/uZrD&+%KHEEřRz)qSB'tI.H݊u>ֺL'6\v5D%fWIys8e?5k47IGɰFp3ʱB%N ۧ?vƔG}69e#Ӳ w)Ƴ,\)gp",,yN{xkiV3ܸ~$r79tLNq}ޠ&YG[I Tt005(5fX085[1xAJF rK;D)\`,OU: *MߠeM5d|fÿags_J9GĘAbA>iKBTӫX:$Z ^qx C'z֜YI^(9jF2 KI Ca 3)\~*,1e/NПHhmPV0L0ʭfZ[:,GRߐS94kU)3^{y-uBX(a9efy|[Q E\D 0ZIGNb(Uni+efT4 +PV>cޯo6 .D64S HG6/Žu@JPҏhvaHNE`naIv*5eu0x,%15"ƿ j ٌ8@'G_BXUg/pw aڵfEsO AIhbFPBAo .KQp( ~޶*+J~`b;ґ"uptjceO ^ָ9K?3n{?Gxr0I*O05$eq 7)lHɑ̸vN*gkՒZptE:z_%@m CgG퇖@{L9r!ikkce+IK1`Рn8> ;$+鸉[osL~.}oGB.AK1,& s qwoF#ƺs±7sPiGƺeβڠ ]Dl0·8w8 _ 5-W,٬Q.O+Yi}D,@KNb[,q]" 5 kir}fiІtQ2u OvCsS \Hsva*De*Y0^mLWw'ā x5T_(s<+* >eFNn)/U e?#X&v2Il{&nv}K[CeF0l.켲7Qg RX+@MNDc5yB )3*D;]0(BdeWLMEh8π<;9\~1睓_˫!Z(Ze ż.\.Kn4l|0cNgFNL@1M%82ӁZ .Lۯ$1 swsenCVKH"Hv[lW3b N-G-Es 4s _or%K8q9F gK.: HGK,pnPql#偉pHOр}Uφ>_bqW3F.,hž黥AZw QeNwj 1ad35V!o >7ksVEG;& r $Qk)_j9,/%9ܐ}c\kt"hӵ~S$f;,lՅid8>s̰B)JGM޷xh ćIJpGH8~ao;RR] ;ݥ^$vr;Wfz'.LJoceҽjػ%[ D*JYu֗R70 R^k[Ϸku7X^G9rҹ BVބ*+9mvIȕVNJ&pDYԋ]).D2=[.L*0AQ k9ZP n<V"۰e0Z0-krJkz oV}8{IaC73e.ޛ(muȦe}6eJ 6-'%|~y~:Jp5\ 2ع`օ$ha6 Fo"xo(GaFUZ+ :p<'HtOSbzKGR>*u,FQoIqҜTeS9 >-37"$=Ҙar2KgH,^r֬ S!DRYO.U|Y(CnprJCף#GOqeo-!cO$9w[m:ĝŃncQe?(fXhwL0u+ uq> D]d6ԤxǔKYxjqStPޟJi.tRz_'uiV1X3J AџE(Go%l 5_f;V"<HwA%ZytIT5Z8/JRqHnC*<ɱM;ۖ7bHoN7G-)GǣQUNI1w Ni@gp7؛k[oZsz+Xy_QT˽PyfGYV, NR뎇CbeP8Ј!JU{Ȯ9CiS!vIIkG dp3 ;,6U>/*t-N qk7ɼĆ5$SwzKQge26_[7StLKmi1\NJh^s2"Uܛk8._aw7}RFϜx?cVlB#^R.AP{k)n ѣ(0K`7n-t2XZ՟_^}~aV>"4◰E@H) r K|ncL[@{^g^թ/;guZkM]jDB2VyC}CQ}*i<J)-t =$}n^8dWPŒUb&61])("*@ҍjkڗ0+gT}.o0srM2! beR|p@ufqgHƽS%s|q n^vfs j̇}GQ#YEkf;TGαrNd6)Hp4gXZLqΤlNF?)'eٳC08?6vg:Ai]R`FQ(#zVC^<+&TL9 hBɜ&8fT(w"K`͓lg 7gQn>| d 4 (7%?';+nߪ˱%'/kS(S}2j CV"!3Zs| -Ȑ,S ,gMYBgVcъ'7y`;V ׍Z-zGdKg]~K>flEarxvsSr}f$>=γB 3fvU7S``)E`s&,E'BY:̊-st̺JOY\XMF܁;,Ϸ-l$F*"?Az!/+\[7D/Vé#X^#evf}sB'PxCCw\DfiWCW-di#驥%*(A9A荰 u-*bο_Z~IBBMBhH.i%јp5Lg>Tc%,ڰwfpO\EүpK 's$8RS"O~*om1(9,MgKR'L'a:q7l":w^?m1›lƝQ Q2>JR^S^ē$zTϥG(39?[E>E9]luOpt}GtU|! \W, BlwrHE,=7EOBgbn(g I 33 |$FG3kπ5M9fN_ Lu5m //.ZF%p( +7<> SE'+[e5K44UȹԃЪ {xwh8܎>`ChZ,5s|Z7>|NlF“"aNUA yѨŮfNG~ysu |0o.眏5OEG!E7u%=91_)mg?RCAI7,F5D:tp8NЦ G`׷q<)+%?m^kK͸ d9թd dpve|3|[~ Y2kN7r/:qg[.{G](`E l+8C&xgE;̼3*VJj)[A&&ҞמdgbsK*< !cZ]c(JeR< *G:r9S(E9.ݯ8y|_:wٺڹ(5i\~)}#H+eRKFn YoˁL<Kx| PK^A 0&)y|~r!$hSY >[ߧnch[x"a׭[C Iu8dE7L-NI:|U S*kGDHՂYEQ5wWwuwr+SVA 5yLD"n_XMŽZsh*̒ BKܫ̈3hz+u.n u;IQ>?_n/F=r-AZ uG?y_s 1W+Aڜ\-0۩<מʌ Yo]|Tk>הʣ<TʀcIX)ȅˏB!ϋ~[#K੬X[3!T2@?, cX1e[fBغg; r^>#.>go"`πvTǃ"t.1I3~]f NU1mD!5 ,Zu3*;kpifD9h-rQKI_so_?z0\_NtV5alfV:ƺ>cst6 Ӷ]1"MT f9J'Χ/R٦M ɰ=-*Xo|;x|G>l&ӬE:;ɑ IuFRfM6t[U"Br>WG.9H {yQ!n&ŝ*i'wT YA5 YmSkr.b君3&6{WefsH)w(\$c偁P?X"Ž`MM ϱR_O%$u-i2KjQbLJe~V !w]KDṨbͩ8? 1T sd7Cˀ ޒ-T7LǠd1+vtYoP`56fR QUw9-2z,(O %5*9 7 t>#a4#g2I`W~7j+[}Ǎ;Ƃl8ώj5gRdְ!+%k() #0-y}"V}6Z-#eEbq6Cx=ۼr㩽\, {Ke ysYә O8D+}E=;+0Rmp5̼̉mA:(k\_Qqx&S.ŶZy{.U*xӌ"2YJt"_}X5Gdvk$1{ݔl]^n+U%LDNbf3c&Ś[Ӧ?&fOq~F4CdLu^9mqA2{ōh q gwFZ!4}^w3 :#Hh"L඄n)瑩9@]qڵ2ki~(W''HڤNiC|82mtI%볃|gn塷{YUui5Ug R0 "V Sak.E4T/sko Cvv89'AT7:AQ%.~R==w*sZ_0;p*2[3/S5[:"L;1WdC`w)Ӣ`fRI˿q C)dmV7@FajAaQNcX((Bu4Rz?*JQ))q9.Wfnka|<񫴑ߔ~] ->~@3r8D[=OcVtITl~h.5 JNG&uـ  IQa){ %7Fςq[[4lo ?9,eot^ǒ5|%Bm}F"$sJՓ[uFs,fИѲDή e~r<{43s5<'X֏&c,ԱC-DF;fg*Y mLtl|gS]fT80Ƴ[<ˬJ~ާ/b/RHGTHzz͐[WK(|pƹ.s{k7}y\|Iڀ!Y),?ԒhDi_'B7+35Jܬ5Q'ЏMX?m/!)^Rd$I2 ?u1YN%ԴIZ- 6DOf3]rWX!cH ┊0y7K^_|uY.YہQԻr!h J`P%Bo?<+l^_6G*u J5pW벗pa%+lA{;ӡfe@W+k$/ߤIHڶqx"|3^击@s 3v;#lL}xhi9ޥ/_~>smm1sxh>̍- 1_␹mXrt/0+u] [L)ܬ 6[U&n !**oլ1M)ե]gVE[pջjq9_߸b|ԺTPn IUJ3=.G UKMUǑ{_)BWw;H"ҽ|&\cJ˟%LYQ@n䦎EKbuC4:|O08kln&.⋁[Z4Q wcY7Wɿ #5\I=/LxuMOY JR̦JǸ< ~H>0hP2XWc|[ޛfEzcm_=P|Ot%G4h%UnJ=M#P?֘%F4]ϣ#=pM3'z$ "-^:?2\[m˄s1rZ=>Tꇋŕg|RQ9ǗvPLt{Z<7$T#qIEf g yI&#D7U}VrH8h5>Wl SaC*-gsl+6vw}\s}IjHڏz! .!:\JV߾+s}JxE{ TUC'kyrNTD4RsN$jd}4`i;$rw)bv9I"J;08%W#zҝ9"dŦl DU"Z{8zOPO"@һQm$[;s-[{UD.kE53ՄIG%wK&lч9Fkףֈ5;-ݯ 5Xp^{98|_n.&b)ܵ] &SVʅfmn>Ǹ]YeDזFFjBNP]zxRY>yPN XȈP $~ޯ&;0,/2\*(4abF9q%/>+O>QŲiꁶ}Y(r|f]#VqwJ؈[r9SB,R3kI4sƸ;YF(#7wP)jɺ }G&CZ!\ӝ-6;HdD=xu᧬(>wA"`G?08++OAV q>qg#Mp.2$X+ A >zՔv|c'|Y~_@XsϼQQ2BC&C">"x_Y TA뭩v)"kCjY*Uȷ5 /܆^A]@Z)ýS&Q0uR#ջR_1t {]s5TuώXc/5czHX鯞\*Z r<1ZPLEF#p#$Eke2V@MDsf[;R1F T|9h :9m"xĭ2wgY)S _HUL`05Z&3ܻ"WP[&@9ƶa9}}NeU6rGH,gX P#.rהCû4Y> ^3$YC2kKsK?@S5B>N5kžD!\+]-r$h LXVCb=/C$ =Nƅ=PMͱB xf/@JƓzEYؾ+t[C]4ⱨ*Go_H \l5J!.Z] GQY jA"1c"0RZWmҎa+;][҄Kbp| YQiǵ(H\C.׈ ـn>!g~z~|TeO}tlfĘ֤.>8b`10{܂ DfN-k.Ӈ;]Զv62匍.Z(d{<bǨ_.sW֊||hc48"VmZ^,FI R-)kBxUns6Z&V_Q?8Y bq;u_[2ɫJ~F>R\Ǧ`L3 DtO@Hq2v qG$HS*?k#IaiZ wGVJ]-2q9i{=]um7z)Y QiwiS.秡`Vtj v824FHL^`҃q3xČNR+k^[`Cmsfl[PWYY XDĕ.%*t8 {lA3\PRR"$xD_.PS5IE~B.Gz>w f<(ViJ΢:K[dǢ.\@ELJ>H oE3\p"xɺC˽n{P? ceaG?#GpxGU\,:fzmsИ#)RP!rrkk=ԠS`&5 ݠn댋BL:~_$6Cu1?%L]2a"điG|Tj#U cz!"Aҫa4Q5w6UQbFlHԣzL-yk;K;) ${Ԯ]ݑcsqM n^#K+l@(μ h!YgX C\# M09ҜB֑17 7+Z!T'3wGޅ"E3Jq8#KoIIhdƥǩMggabchxX%ZY5{ωNͤ0DVEV؝@.ujUωA){FJtk4bb:oء8඲a};>yL?gʏcek7l~*DcY,,GIWwa-VWR0 aEˆR 619S)elAWA`E*(B+zs9A?6rC))iV%m ~xtd枤ZaHפ'{K#Ejo12\֗$%`:(yO+a%Qf\߯%  B{_yR-A-/sXL-lLj(J7UikW 3X-Nn[s$R@a]c8ķakYyY2g|y|uCu(=?98 =ii y/ۄ1XO8+?4&CX3򬏬+}1 ޟCJwonyaX;sC^tc3> @^yzG^Y>_S-856|Xb:StqKhþW\]C' "aFB(RW{Z-~]//=^ދSx܌0glVRT"nD"P/:z,B2L*bE^g;Qπե-1k.sx"% mGI,$|6`uV->*܍?Qz_g=oG,ئ*fȹ˻_{D5 diYaZlʸHqp:tVUz&9sxN5eEp^WF>EXA+uQQi<2UÓ[f-z />I5p-"6S}]tRs9lHH5.r~sO9W~R?nh:8*RQEvw)R9g\gP˼7E"Hzy;([h'FruYEwG/7>nי-&RTr|;b%D_>swB0??7.zir}9t[G4 T<4CHLPJm|Nԫ7kiOӵXwUE]M)KF;JVr|5z3GPd F#_}n47^/![$]еv3ubxJ 0u%ݩ"[w|_}&ekx1˘:،f%,kɇrc1d0r>BQ]zg8Gud&:\jYi_r{"U^\T.qݒ2XD1}l> P8V^K4 ٵ[X y8C]))>jCyU8u#-c#c* 5A>o8]h-K9Nr)W_\v;[谿_z0vs](Pr]}kD s S ,8 h`1*BjY{^ޝgm>hCeX[Q!cmlF+ pm[nzЭG.UU3:lG&ϒhHuB]h`.Qnp/12il*ʂ4t!Ӧ'v!ù[sBBiXn~F.r=1ŧvEYTCU:uonaMx6hJ t_|~ӱWC)+Y$碲uHϩw1Ȍ `!idm/:<,f%;*g>(ˣO;Dzbuq @M> caȂ@Tɴ3X\Iv~ogMfp2;:>! Q3pwLe#t)eOx?]boc4kJ4A*. 3\i uU)(uoЭ?AgLտrTW{s'zˤܶ*3ٖdd'7 LLG"P.Z(h/?[?Q%JT^:qfK8 Sfay(5aT}Rp5~X QbEGQZBb[E[X|3Yqw uw z)yƸwώ H |0%aUkڙ1 Z&l.g$bZ\cv*3W-kǚٛ]Zo-2н,\" XJIy{z~pcSGwyX0 Gջ:Mke^?}J> hdr^Ξ {4Ԝ mK8ql ?JcE՗`c?@As[ٱwC'.AȺא'XH5  XߌeWب ZdPdv$kHԭ@F $+ʣv == +b唊&^tXъ- /R%(=∵1Ro{](TJ'E_AˇL=6d%}gH(Dnײkyt"Z.8@=w˜n߆> Q'OG֕Bg'Gc1|s7zßyGW#!\%RR)S)|:PBdĸle$(~2T?,cK  3 ZvUGM_lo { ̂yͩHg8CZ@Bnj6²W~DuIH֢c9zv% ]RyQcn,o/5,) 9!B4zd^KYRZON^6>H#e+l/) ,]L>"fͲqw1f`gCyy᧎126*N>8BTO#]/ʁ$7g śqg GG1ʰWylo*4 rup7aNUϧvkFi'"3Ien Uo^>q^E5^_9}]^<_|7u=n F [JAJg;XJM?y]Q#G\fr<FH1f[ [^>>1Ya#k)P_@uXSd)&e}uK>~ǽzQBjY v:eEgeI,93RnM:M,##+ Mgt>7:+![V|4M(͒T5^B/6\faR~IcPeղ`cdՒ ,c};*PDd%bo)l5zuyĭޒrăL#+IW 8UWV>}̮*u1"_ cH_g 3?\P7y]& JƄ;(s+" ¦?__7GGLRvI_G!.h`rfTԼBVGB\KU@(l^^9sKjA֍?j.&m;qiɔzA tn~1v>Rzq,pG,`! kAuʉQ{ 嵵TcRAXDž' LV5iL'KTu4^BQc+1?I]1_I,`u9#Is_B~8ʣ䣔z妹uSȖ-S^aX%],Lua`H"2[vրW4l|*UbP}6|I`nMo90x|^~>5;bEc+" Kd<'Vyj{}A.7C kc22ҜB%Xъ#n&Ce=`x*N˕ >+?V ]:mO&׆\e ŏC\:[C KuR7h!6*4m }b$QS}] W{%*d.i1 &dٕq<@MicMȹfJ=wxTc ) At#Cz@,f[B3FK3;{-$Ls1'̐ g-O0֩-YBQűzXVJgLt5iOK6-[1˩v<1vD|I8wߐM3[{n ?'Ak=n <;QV0+@py^b|B`NݓP uKAa cى,!@)K+Zt0k^wRɀAީ/k`0\ˋIl(泺F)fbVmC)8s'^y@|h NXoc M;s6YQ7Ⱦ@p/JAo+6y.!u r*6tnz` Rm2~VS9sX!% 'Bc7]’i*,IhgdA nM)֙!L#+)NoޒZ< ]EA&)ҘĜoķK].r]>L9?)I)~GC͕9ԑ+a+nVmq ?]`p~ AVy@+(%w]3oB.R!gg~}D⬡ 쨠 R[G76T3a_9ٯpinpW–@a @tPב:zwst3|s!!t?<ʼn5[d ?bw#]qvq:7k70BZ`.wa]&I sݭEIJ4-Jx=m} gu8|tZ7RmŐ( }<=6 o1c)ٮ!W<~!e( Ait nUϴ@bDSŷ2JqsR*%0eEGS#p]EuBl56U3BƎ nEaΨ랛hmY_G>%|Kb Rcd+Gy7NYQԙ8a`0SJ"Sm-{8)*2>5d>C9x.7oBh2C,!`0V\ՑyV:s:a9";?wS?VmXiXO,"+$6Q3'~j!\OJ H8n>*0I&FuQS\W`zڠGTe@i_Lh>X?V,G9'L#h>{mRʊv:<~'Z ׷FysYqէqbEi;U^ HġG%8<4%usD2Vf+KLy<3G- 8QcXu)bXa5pDmy+w?QoVK XXX.^k )s8]&EABHȷe.d)叽8$].ncX'HU]b1v)\X)P׹Rhm$=>er[#%vx 0⩂x&0 E esȶLJ@O1(Y6^$xqi@@9khWJFF5Rj`I[Sɏ,0?}&."kŏ*b47# M1 }S{r}Ko_ 5&qdCq,e/N5n R ghGG]k|K/m}䈘g;KR^+ 3SQsMWU|P8$K5rc}(ϊRJuy4R a[ۻc磇O|Q(zuRi^1}ĉ\D?"ԁݥ*.ڟdJvl;n0rZQ#n}tsʾOS2 Qީ%'uzP|Jn)[!am)~_\. Ih$w%lPs[hDB`4zZTI;Fn[,*x'sŊ[?EY"h!HBX^=yDa2 ƒ߃I< z6z?sٿu~@a'm7 Jnq4,h<$ \O(X?|/s͏>p壧om$| {y.f#J4[[CnU}D਄U2Dj\ѳNJc{"5eñ yE\ N% U^jog ̠UO9B #w4J"\Ȑ-`UX^l6p_ȸV 7[7/Ӯi? BÝϏ/˻$}b譔h[>I.&UlMY[5J!$譢^Q,bǙ8n*FaXW!ˇO, 2D~'~bbÅFsuVPcnQMS&rZ;>:#y(x 6a1,dP܆Dw?z{-A]pG:8ҠQF` ;+R`hy7z[mٯW NBY1nK -* [t29V>fݾ}Lrl.>G$=oԎ>ktn;\ܤ6ZFsrř [QV=uLaC[K󼮴 ot/XwnVWFpiy\W)XHEu;aQy$ty8#c ~5"+M#pjc]C!Zb ?k) ˏo& @XSA755GBfx䏯m^/RB[;ePuT65Uls2#ӭ,IۯpRQvB~ rpG5Z^؆JB90\q"㶆TH5 :a*]YNxz*Y+޳nH/;fEmҀM΋qzQŭė~tZwdSKY? ikjk 1 0,ι3'x y) >a- xl0VI8 PqOflW!G}Mu>qWA\&if7)G2'vֽ|k#x=j+[[<0"{ S.g/2I RX*GŅ5OIG\h[&q ~wx` y`$Mte] iPW,hwBM;Do q,- F 1Lc7z*Aak;zRs-kU?aCm0yU6^Wφ`pL3S)4%H՟Q!fEu<1z]Z3ꚸxy1X#]N1>#gгTͶU:c䰔=A2=FWC X(=|z=YxĶo&GD}:U(OŽCf$,5FLnӂCMT_UtcN (P <ųgө1C>R\BE2.&ha{Cr]v UStnR5z'x?0`ڗWW.1,oΩXj?8D] K_:^x@ʿ9eL)0\?7DmHm2~ƀMCGCbv1,*-^UX{5kHRlD?㊼y,eV0,j6HrܽvY7Gj!}^w~>qhyL8n3%ՓZs)o,lK?pHn$a@8q 'R(!ӯ =S+4dcdm*H)EiI28hH 4vh]czd9BS@e۵#>'.iǂk-]fiGbQ}-AB_&sbrSݍ_/ nؾ.-SB2 (RA^YH}toJxAWdӗx0 *>:{JMƒϞ9tϡ#kv)ꦚ/^ǚCRЊ8uL6kzAE#1TP8oE(aMkǢBSWz99QaLAdə faFHg&,]M[xj0Dځ]VTȲ' (I(+IIV1>g@ęKyR,f!W0̛<i qU+Q~7!}͢ξCaw5f ļ<].46a M?:5 ˋn˘rRi2`(!G}G>8 9I3bR3t># ( {EmKƠt/,ŸfQFN3T)m9 qcgfv7, z0o2(b2nꖻm|ا ό eadSl ™o. h$yLb!.lIy/ІzM}kTz]g_a^ON>^ Y/ovDUG8sR1j&ϙ.Ԉ>*%S%'cN+`ťU\Mδ.y!,?u^/-RΈs?MK\Ւ'S,5̝>2Gkr1dRJoV%NoL*}EqNl{ZÔmK}Ǣ>Ŋ6F!BIJiX^+fi \iMzOf^Tvuʋ %zWI૧{DWrBPfT@8ngGFVZb̘hLdKDKg Z΄!̰3aHy{=PddF=NJ)N9u^v9j xҲ9rF3\noTPբ@`xcrlr)@zj?\VҲA/Qĥ"i t F.ZcoS($j\\~4&@?߲B父1k'R. v .$>H 36zހ8!z*FoU r @2W;PW=2 W̘; =@UX\f*Ҧv ]q$1}:@hWWVT:l\[ٴr䒊hj:f0̻1uH_ :+B.F: ;VsX@~uMX:p'Fɂw Gt'Đﺞ7 [qRbiMU2fn=#zgbnd͘].4\q\|+dD/Zfb+.a[| לrego߻[Ff_}*=utu7|`[o}kszr7Z;ؖ֗ "[&/!#]5B &eM|,Mo>M~DqauKDX\ba~:jqV OT6v@R>EMW0[ZAax SvZ_Y·WӰMs9C,.Rs7Vh7&G`օnȅ.2! mf0D&Xgʎ]g -!3PK3ɣqjyiP-<v'Qbyl/v0,ȊZAyj:b\ .|ݭW*kjz[ "vlgqKXƊ G.W-Z@5dPJ>?zÜoOxVrtiRQ˿`YK%Z:ɠpd@"b`оlGn-ȥZ>oɎ;$eT S4-Xre>e4PLӤX=#g ?myұ5xleD5JN;H-OT̳Vǐ;rP;(}x57󙢙d<|Oο'%gR%pl"Dc!G#ժT+}k-e#H8m#r,^mƑ2uipֻd.+M4gO=Jq ]|`6sy_s׾ܼ>O_3px}v4oENI1!Ð;X˙bFEg׺d;A̢d6c.o?Q!韡"q)eB6<nʎGtCy ;6 Xi|K@:i-nD䈗"St`W߶u~~zk~|- 2=9)@%[9wx>)Mʒ;Yb<'cRPqg4#c"U]^"Fÿxӌ[{QM%-u?G*w!ߘ1TgkYLe_TG!fzc>yZYXUg{_׋"ԗ_?կgk|@tFTASSZ&8 C̱;IXKذ; nD-gb}G3iCz$%˪Ylt1CI8ø`@`S'!K%/[IPP5,EeR?Ή) S%h[j..19\.GJjRts轠C! yrP{ )oBpq%qWTM_N[Bץ%,~=-|ljUB@ytŌNmc0QlYv6b0t(ZuP+//{%~q_̜i$Jy/ n!ݨ~<{4X=s -iHQEЇ~n&b5mൺ+3 }Yt1[dD0vj>cmPi!3+U_NoxUnNai6fy\Z l%#bqr|]t?:?}K.VDi%bNWR5՘cRS~`ay{+)odI~{b]~w_-Ƒ@p@RqXX"q BG!J]FwRKT1AOKpO9}x _ax/TlVXQ'rqn=qHjZXT6_i{Xa`(Ex,dY`0U[˜9;#Xw`C~q8ʘϤh?Fj9PQXʹeFZo\N- ɯiЙy8XpM3kIu~yٿ{=s̚LS!" @Xp{P& nd}'[h"+t) |oGyFrj!0<@'z8 _FJ՜J 0,qq ٹ#RKB*3[@L!"?. [3lQ2b^g>\|_oeo:^GG?~nӌ#JOРLp:UpsݛRj%^d? L!&Wq-C!VXBLi'1o!d(Kl;+R$9mӵI9Q]qR $`wr_KXpVYRU`¯ZjadaS>d!K OQxJ⟙WEaG-͆4>ɳ#I9Wt0#I@{'PʲIn6vW%,PiV5NÂ]NOfJ +A"KCa-`Nw uރ&p}[ZMW 7,W7hJ e ,3@ۜaYƦVL9-TW3Y܌oIzA/ol]w[=TEqU'4rT>Nkch Efn[8Vh-S8qBOd fR-߿3%oasLl,N!qz1I={ko˽_=mRZVq"5X(YX䥕2^Sɾj?w ;?||=?tL2%TY%Ȳߙdu 8q,[N1Ҍp G 6u)j(`(cZ9_Nd [, r~ŠX⺩[q7#Xvj9M! n#iJryq(4 ,pɔcQ\ l +Xy:K\"H**5.+=sYUsyٞhm3∙wc-A؍2Kdw CTO^c|9w7n{5owVQ i2oz~%M7 A:&ni\Y f`z7Y1&{6 BOߢ z`+DhG(.7>`8{^`ejlK%6DƷo6K7_<,Dj +qB^cDJoz>߯oy9:e?h@#&)歭bŲZXDn ؍-R1t~~Iu #Ι+q0\I;*^ѯz8~qF`(2#6Bܮ!ż?7{VJF\m{G{:Qq ~ү_~x[ =qP)*Qb^ez,&x,j%|8N @1mM9Ŋ)l"8`ILʹ2l6ð]J:dPb4||pb,W&%s"ޏ:"?>~TP%Zͯ+qsu[,#Q~]cdʚ82ty`EՄ^51 %mRwz{=lHnes 5] ՠ=Ym ]Nfx'O}soׯW\T{*~XZwp})KtU<1-^;dOm%>k}>[/y}_G?z%o? AZsWs&1pfS,^/zGLno/o+"܃ok{ mg%}Um?\ʟқgNGRY+%bVDOYC9=`mgF dVzxaUWD3\lb(>4}C:Ϙwj !=H+I.,omu>>ߟΝ>\?^nip~WL)z>^;~}T m+ђH(ǣmX|vĞPZ'/7O>??zZ c)L~GJ" 6{P381>%6<Ё:QݳT<(!u]_rpK7Xjo?J]` ~31޵Y\Oq2ׂٚfr{6FKǬToK.%_e͗u=mn^zU]0˺0c[-Uxieɍa#BYXU2>ʞc΢Y5WqY~xԶyJC@8}!B :x?)@!m65Yj3b8G[  (J2gF}֭9:nYW#fo(!+N.1c'gp-h"OcpM0?2Yq܌ɹ,Vl!)eh+%eRIq9.ҥJ]'i޼eBOU!_+G.hLi1sXs[kE"cґ]MM * `Ks{i!ᚂ?fOB”xɹpQ,U9h'q=3h`G^@,!nn>!1 SYB=srCϾ7m_.e^SJhahf8PJZq>g@ìClU*47EΔ̝+[3l6Mªh{̛09B+U4q2I[ЭZz?M'{A;_B>jr+Px01S`|ucP~cl9%Y %"]<Fx ZQDZ-},:`7ԴO`ͺqzZBvmPö?OVJh4ú7[8tIt?8ryx#qdK7E*J32{LCqN+cX`)8Ս_3s[_(\7n|PyO~/ՇǷ0vWrp:}nu}׏Zz13T;mV:F0E.wz,mɭ憪Zc]z .po4M+:^2;SŏLx]6G%֪X77'tl!5P^Jz]~Kόa~RJ^,r-[^m1"ŔHuݤ C"J}ڪ_O-Kᬃb ۞Pٱ$f kK"IES2j~mF'} E9& ^Vxs9{!^eUd i+ښ؄0,{I /V|NxȌ\T[ʶ'ʎm:0;o410\ y+J֝H#yWq[[&4CsL+wP(e\:lQm44Bdga5mˆZś.fgٛ;EϮ6 :iGD ?́8x¶)!}8}cC>{˟>⵺ڶ ~F |S_:G׻axQzB*Bx M0A:T[! yk*4S1llS&-j2YsP+ xrKįsJi!t|a~h:=>z8cTșTOi>@Hf Kט 3.l@X56=f:)jue*PTac!  绢Fhe{,ڡKC8Tb 0$TKCxվc@3|ŋUc᲌*{بJvv,aً;lqJaܖt1 Ifi؆,b=*K^_^B28<80k^ʿ扳Q&"cG6 @@tT6U[xDMS ld%b~u6 P3^4(]랎,0]‚7J J'8n& I730ٖ^z/7(M JuO'_N7Kv__?_鯾OOwOO_~D4QtBӼTUP7vP^n*U=xa)vb.n-]ϫ1ih63\ A Pbہ|!D晑hR!pa&oo_뷯޾=zu &0ӫy>xz ip<<A˧%(p$eGWDDW &?xSEz2$0iOC\!Sgl] lC QsVߥxC82\\V_BM T D'QMGe\ HIl{5J|*+לa981UˏhwoNQ%4)-9z6D>-ͱl9VH- wZҴVG)N4.r"V\ jKy$͙?"};4`n){Y%>/z}?s}>?y=NC|E*`! !q䭯&7@-7gٚ8B8v|ieKmniJ=ӘPǾB4^-8FY2sǣ4||5a:Ոi=OSil5Q,J)q>4-$6(QfqIBElB+ktc{ n U4FdZ-KL/:q2Z17Ep <( !XIc<4/;M+$Hέ!䕟=<&Ewo2hTВ.F6]K]CEF-^4%#!o"@Ij3jwe?M'r&m۷9$C/>曄d8:nddi[?210 (1Z2yM3b,!,BW O?y:1^uYCZCa<_ڥ&f+6=s9ts6&Ŗx=-wp6%?0^hMJ㚂Oʀߔfp:4C䌣Wu<M') O&;-R1nSD~8eYkyK0`2zSWCK JFOEEA ޒѭ026Gٖ nH4lA$-k {ء 8+vrOh+w^FU+LG<`M`yw`h}Up4~.TVXP' }AjjȩwV2U.^˨Z d& mkӃ{ၞ0'ɦJ>c&ftߢF~n9:"R"iҔ"6lkAZ[wQeh,!w0zw{u |`ݭN "bYIsa8ooVo~7~0Rt/r(ߵl  %43R5M>{^Wղ~;=ɽ뼅Я{M[~bůòov fSsst fȬB46^V4hKUzwnm$(l]|JO?ܛʥFEOJ;=zdmɨβ- K! ,/׿;H[}Z[1d]p.7 Hҏw$CTⱗf}%ǃ(g:0 fWy{%ጎ>~81PaV-lSĆkmVNz,eʍN(,&!gF6n}^qXT}ތ;F"U$nat;1+GNR3W8ósۓ~heh/uך xoCfCRMX{br$,`6ibاٲxI..&M\z~DAnX%;ܳ~n1H!D~np󀀘6j*Oyq_;7*-O/~> 6sw0CxDyن4LJhno\Go>m~'?>N)0ؖ߸׾-S(j2m4.cHS D#.ťέ=,M>|0b}{o%h&JD`+ܸyd1Heî7=I*˅)N ^)&Q_Dv3wĘàv8i]˰]F<<2įw]iTWCHɊd4|< * Rm' n[moFx5yL59XV a+cΠs!MT4XK5 jK]ߛ]{~n ײg0No^9vwVې61{~bȺYXfݧ8(5A̘akZt0vn(5}׶:`1*Hؾ*b3Ire`غUQPy"7!C323;`NNjzIv^㔡^YV\CCP p!p{ 6bag g@_$ѹ+$dq4!n<wP.t1LSS!Xn4G7XN*5ѫ|1 nu\K*9x=1v|PC񉩌k| V8"r[է"PsogFdwƪiU2N,f^rgTٺTК)ZKp5ߪS[̆-8X?_Ě ?õYL_>bg jd$z,)8{XS=O Q[~?̞0lk(HZdeO͜Q)ŧ d$i])M!ly?<8'd%>5c]u| 6ZCs1F,e2VPh%_6 n`p7 9Vy zTHrsHlӄUb>=Ք6̭$Ix:e- )ko/㈆,K@4&P*ڃc@7FEo~6넜q+BZR jQ= RN8jȋaخO+h&E9aLu"ͯ?O緿`yŭwOl})Yv-kTPcfd]4=ץV K9+a aG TY(.kݢ3D"ه8&MS81  ?cJ MF&yGiH?^0\<$hy(.AfuptWp][71>+0i<m>^VU2iEg 2I$Ӝ6(%BR",Qrvj5<z V7ݹ2cv Г70R:m~<>ZP Hq$KbXS V*D% 2,*L[5/h>Ҳ0\\RLʼnfa0e1 w(iKdB1˨nyȦXj%u]up|p?Χ!?M]<+Vuk׭]2/bU"lvu'lr1!wn^P|nrGdjU$ѡ8'-! ϸk$orWOܺ_?]҅Ec.V =såK`8˯;/OS+OW~~Xe +"OTVA%H! ŌO6(&!"\T#lߡ1w@a1rb8Ll]b ]3$VsjuV Ꞷ4zc}XphAx#zJrҴn*߇?6?OXkQ 3#Y` `7uBws0U~MTjbxhbC*x \kq8ԭMmI8ڦmh:JDLc7;f;Ϩ_a9;Y^1y|n$W%!xX/%[/cYe8j(x,ZƤ1&LLӔBe~hǝGʍ)<-)㙇hrt1Y~JI]4>RiV Hui><}r}SZie YRLJǗ 'а)&D&r_{ e{Lp+<0yƟk^:`T*R=~yHǨj&>buVL/k9Lq:z4>轰P9s z)puo"}0y+ǁcsbrVH띟oE*F20͐!ȧeۈ: ~Yty>஀(KpK3lܶg?x p dzcx0J}69ߔ4*ƻ-+M{&E&3n6ԑsbͣ&{o7D^gbDz{Iqw_''?=Yi I!/'BtUl`LFT`llցMZj~Kܵe3u~;5fSHGweimI4,cViL&J욚;7>X305vs:=iGwu$0y҆S7ܣl(ۦI_/\S:u,= YKu/KM^i E )E̪۰ld_-rhVYA%}BSbdQĬvӉ՘E2X/K'Gyk?^;X[ .)"Ѷ&GDŽP {!J.O}Ab / %f\P eHӦi: *NQ8'wh~*a.4ب|/. VC:2Cēц6^c}x(u{7eyc!s8xġ;HDL{̦1J(n<)/Hh~dNG_3^TLVnpSs fl *PNiRh^*f(ge+TX,Amĩ9y>rv)G [Mr$ܛPbmJVyX܈o ˨Pwל7R@L26X~aL-{ NI%_e(cEF9DY{_ (sSEg&]r+i;H0Z/ܥr d娩jEsѴQzV"v]*em}/Q`{N:^s饷YfsCeԣ@E1]Tൌ0C{ P(^-]@HrllgfZPW5nz+c^n;ިfopiOPv+6P65l)aN? G;+yds~ 䍛5p)naK(f55矎7}W@_6u.>=}uy|\)_ܫjnMetGC ( {,Fz3,xOEe1Shoxݬ=t'z@ LcOM E U˪^.0œD~Ca nE2gCp CI\D0q{oڶ& p#"5`)d}䱮%~jYhFSn')KjP91z,kQ+gӠ0N%ݪ8 ЌO)R^C/J(kU2aC6vM'E l p~??[' Q\ *a]KV_ae :ӛ),Z'BЌW0]u¡fsZF xge4jeZ?O0;^3{u~{v di{xZUDS ^r,p]_,u =?g7egCuh10 96qC2GHJv3 3Zλ]^zf`=f%=AiAm77뛘yfjYjCl^eEή|Rqg4}1*_BF/^>XCO)V"i $״^ܽJf0Fh9/mY`FB@MRG =`5NJ1Na4^ߗ˒pa'r7G[#? zÃ9e-0|ů4O=|Ϊ$ R RI= '(nR+_` uG$ -W Փ. Z|X*Ei`ɯ[Pe9}TҨ1ނւO|J0a/փmfBCC w iHT>N5uM]&d`JaϓIeQ)=7^\iM/Wwσk_?yu<\ח-D!z븽0wcZK|gk7YϬ%^{YSyeswQŎqj8մ gtY鲦rfj#2ݓ!1i5/m,I6.0qvv겑wXMƛ! =;o"FK^1rUvv Jq"j^}D酪%[!QRVڶ㑊~* ѭgZ'vb`\7!+ɛ!Tvι 8(j ^Pr\E%e&JoplKŐ W 7:kʱb?qNSpa ၂~i}ZGaCv"X3f#+Үҙm&-kMP%@2 (W>&l)Mi2K/e[~7,4+D>sS 8U}y>x #N-{:x8̓J#dca7,{o>:VlzE\?1j)|чOW@6SCLIo~MFGLWujx՜ #KMwGaj@2[B 2qAjNYÝ4Ad,k-)% K՛qq|0MTPzc~ƌʵ}303v~H Q7Du[Xt_ wi"r '!7i0d9:bH=<&&i^LۘeFJpXԩ5$>:0"RliaSfk!Ֆ嶫P~[j$Q8hǃS`KYdC˟ bA8̮M>T%wcJkr4 m aM/]YN^,jXgog3ơUkpICj7(~֗rݫ\ -{1Ŕq5ɏd]X3C,n4xUc}[Uյ`McZ8V+]f4۬ MĴ^ޓtδ~IŁT{3s\Wf,J\ULQĄUsUEZ3Id-q',r4vTvjq1ˣb@e2&, P!K&U'5x; <[ d$?` q)GZ k̓aCZzRHψle^l8EAo6m'ƒCvP1amU*z +<OR*rNm[}ȚPҟ6P)lRJ"jlnkB+zsjW=:жE\ [ls ǥ\&.%mӄu3QIȎ y+R(ooYo3aD&I˕Y\a=M:TK|Ȏޮ-AHS}Y(0%xBxAB֍2I0$UCeuP_0iOb 6 Gp#LG)lnII9#5"D/"?2V-Jv;PxJS?|>tyӪC^n=f($f3G$_ȕq<77d̎|8n_lҳ64/-41PL]4՞mֶ5#Jgb/4f Nn9,Cݱlo\?45!P|MeЛJ,tm,A?5wrKqq /jIuH m&>㛏_KNc_a#iI ճirX9"؆:DR)Jx;6y|&!*rc}9g`:kd>#gro208&+V%S;B@PV*k+\(٭R҄yyrG\\ǿi| zڡXLP 8Pj} K_1yn+?גNc$2ӔTs^slK*aXgoN*#[*zK B=*[U?UO;Vwmz { nI]1 {&6lur go` S- зջ *4OTh!NX\բ @M VW7[ Di74=:㠘TzmVt C_~+I{lP0c Z7tyc>{Uc0˱7 ff/8bЁɿnc/:Kd.Cer^|~^O2uJQi2rABٍ ՀN FgB5S,piP^w%m tGf%5wVglJ@$b-=wQVJwC#ְ"* $Fd#RifyzxxK`E`j)cz)5 g[աӱemڠ NR7UL0CX}tuXf xp)%5v[u5vflpXq P5icӓo*4)\S-8~;R|.밢&wCd7/zmC.OOG)o~* `B E=5,0Wi(Vʦā :hpno_ae0N)Eqsy#> I.ccFԭ"]갱L7*1re}[8${g煣Fmy v!4VtUK_J]r,6^e<_͈{}tHZKFЧ#˭lH" HҥCVÞ8Q]Z[1 3C b_H)TPoHMn74\~'PӺ9x@h@{!z|^WO}\p$unǒs+a{^>ԃHsYݽ҈G+R䑬Z"=160F 7 bѼ3JoDH=\ NR1>~d)4`X|/C9oۊtBW3VUEm5°,SatO&V(ɄHMqB@aּc*5O˟oeSMqlwTxW ՓGyxDҋ7bo,KL+df03KQ3MtZ-tz$eǸ׮aLZX}ʅ72ײu?/]X*䒝44ؓ LM5-#cn|LH}eʻ !ZR.r/z}~),n9%jP:Ωwicx<:pd 9=<,O+[Fr5"To/z+nfdsjRCw3wc=TlVE=,@sskrkV ;qC.|Yn2iS<ƹ},4vmJu_ A,1xS0L+d%[Wz={x&A蘯4žnKRnм;}t?x}ȿPK~{\KHy:ׯCxl[(PضhY ,T^heo05(7E.!k.N2V[`u8 bJ }%,p]v}/岞.OyhŪ17U>e q~(Kd8ŝydڬshh#^+|t JO!_L $jTg . ^(8A`k]to[uIֱi^kSILr:xt])^00ҡlC-ܞ5)/WOpx|{?d7]]=8Ad9 NO<{a ՜N##i[D68wu[EH93^\~Y,Z!#|1UHS$|//K%v>6Za_|ө4jR 3gƈ"EĊXmtPMPj3ʆQJ0 bm1i5a(~)[I ӜF_Z/ 1dYbZli[3gf&Ce ]F.ga̧c/4A3P4E[s4[a~qy#V8 $}Q[]' ;6\k/6lf HwK۝((P4,R (i-dMhð|m:^?\)ϐ K.eh0#RU'6Me#am:-=|t ۶oy*<]s.׵aXGҶDvùO7;?<Cl%ǁ *Mor?'[hXe(ZC6׭ͯk֥+8qJ%O5h_~ZZTՒX4;QhlkMv @2{/5kgY۵^WJ,e\.|~߿6_z >*Ҽ!-u|CF|;挠E6b >ѼZSbx>I_LFqr= -,"I@>궜1hҏh3nT-ef ָ}!npl }J(ƛ\{Aب̎*k)uPdq丞pnײ̈́6W߫%;) 븋ɕ væP292K8#_W1#blDns?iVJ1mk4xԚiHDRT٘.z>(4ҐcvK?DGq8_~X>(?,_WRM.s8!yt\ajtlG0=<~?v|~__8@ zt7_ӏ{}4G?~<}/uKl״M5!5Z_Ň֊˼osSܲ64DZULA$t@1P3s V3G=J(IZW*iyZQ&P5hlgbikY{Okߖey]BY~+p]9 \iM (;IR8ȋ_Z^ΪwA-8IRZLhP3a>rUzw,sU|pV,][_HPJiZ;qʓA%||&b#d$ἅmX5C JZ [r,jz=ǀ936qE0Z  \ʔf7yK2mqS4av~:!{iƾ,Ny5F̙*\\{zOCyQoJi ` ouјټ7\ /?Q:!   טX(H˳J﹇ԗk l!Vr\kQ$ɴ"uZ%`0'_׏ip)2kl/}_.m A%NuW٧_Բؖo|]Vdu>Wù3cZx3r~Z^iYK=iCI)HqbX?zܛSAD6)ñj4#nScre+> 5a.˹./k}\޿e=('PfDK_{RXѼ"J ^E6x|^e@hmy1z!&ǚ)i^FtkqwK6EEݱZ!<@w` c}> 66 &bSR^ wYBizN+F7̕ʰsAk0Z j`9!HhGët8/$77QD89]ڵE{k:R(BnoFW.R;l2Pp\Qmu0:BCaWyQQK v&6,fWcJ*/UnuW&֥^x^~`P+@51֫oz~?n+Xb>=[M ˴^prqGe+!rB{-T0)ZqAblp)BWbjZr]|Y|eY_^OߖB|m9}[3@K ^-Z%:&vqM޹k})E8&%[)3jsG0߀u 7NwD`cL+ w2H8ѱU*PB͑Қȉ6mnƆoS6yPHE.%oH4A"Dꗮ⒫x.P8ٹ2m;($,T9t<qts>!U4FpmdjujáSl:hkOrT*NYÓ5*3@H{1*1TL],Z[Jd `^z59|V\I-/~x|Y|ϗx~3#yQ|AX|9'~~x^χW}*?/Sr^)ڏ/z>[/臟M1l%w߸vjڰRlW'ͫԋL9&?>4\,z];r,i )O7xn9sи|WhnV*T"67"{$6ˡ+h0,䡳^.ϵ,!r9_/>a+Q; lʀ#X-$]D#=, iwkUuYt8*'ϭ quwRfAv1c3D8(s*5aQ'CUVɳZ-YE[KݳČ،FnzPH KFwFheZĠr텄T.Jt1Ma F >T@O+t)=۔0P㫷Sz<=HtHdzFs0?}RVĥBt 0YKg6ֱxWݺT<3F=N)z&rjm%Xr^:5)+4ܭ3D0}~6wq1'?񬴆U]ױl}YjTf(`nQm!] JӛGzhi"0U,GV9 9JjelH%5',mo5+(MW\]Kk}5)2>=]0;\O}]jcQ{_Y4t_󇟽N_w?>ZQzW#C"H7?Dh_o鄑CUt~R+qGZX"qզ9M٧Zrl)Wu|hE'Pkګ[r!1ҖK$Z\^֗底`~-=;!9Zۭe) jKhu;un{g!혒syo+`.;7┶1c B-gne[j]KŢlS,ʚdʪ|I a:ҷ^z,J* 1hP!=5i) RB@fbـ6XVZ- I_9,3p8Lc1Lp8=H=xgs2վuAͺϘQ.a{KC?Xq.|([Vci&>7m#ˣD 5yz6gtj wM?>c'l]y&jVȅXCzhVk=\Ek?8EԳD)2fqRC^_/=?[/xzFBr#^ "[P& {Yڎkkk+F$z/ E[ѯ˸Yĝ!?ynܺ^WrteM1Ʋǐ.TͿ 34g0|X l\H3w3i"E^Js+y 1k Dܕؚ}_8{8@\WjZ7{ "EX-'`v&ȯwW' M7Dٱ\H8ecFʺdd)Uh>^x<N)ɓF8گn|*ҬiSYFʍ3ր-ae|qLƉti ]{*"5Uh@벜q'6JD͆*u VmSZa(KM- u\.J6. ~ @}JǼ]Rg_n?{_xpfzyiU?5JіuYڗm߼zp|r8jduS`UL;x~\Sin}.󰮣;kKoc2ښ}W[\}bX֬ENG3d `44<~kˋyP-\ |>lei+緞e^uϑW VXJh|<8g- Ƕ'/eUoS- TҺQ_ \!;^)pD][^F,je9F)a *Zxó\^n?f [OrH*_IU˒xD{3-MrʃʘLYJ(R";Oѿ93LgӸ )b)gXptg/6|m¢L0I*?u66j _..z 6-r7Eu.Jn-K6ޮFVK+y^J˥A#7j:KJCo&mh:4ߜrfD5Uh@1k2Șd:r-fv#P]2|sMwy ڊr qoӼ ]yBkc]QݷeψG#P}=Ny i "&Pud PH7S@!ْ |DUnn8ο|z1c^CrӫbV8qFP)8mX2)ez ,I% MQmUV|nEM&ycņc[{2gE*(òBF7Y4˝S-\ G!ʕL_N:wrT1 ߷-0=Pwp:v~U$<5&}cfܖb/H?;׊ SXzI>\kXte 9͟jR>J`jF{`D%!"D{}_/))pziOi@y ]ًf^确=]/Ίr^UPzZ[//.w\v=rr<|N8L:rz>*f(T[Kv:7b%Pe.-a`-S)@0PCLuVLȈxc-ŽȨѣMwB~鰈NY% 8ʉj"л)6SH(]j8GL;egGԔUn!,v"H=pKEdj,u(Dm9'?8pCJa~x<äS#lwz7"h-ƽyV;D .6Z]*r7dNmuD-3NoXe)rn VH l />0(Bѝs6 'u}:yC]iV0c0~1S@E B)[6?P}Ag{rY߶um lA`H=b$%ۊ!&-kf]MISw;?s$pNrV{3^eov-3P,׌v"# \[L1S0/On$\4%=ƍU)d %Xgs&4NPS!dm9nc>VbwyׅTA]3/wG0lk nDb#\RW0 hdF r$ ?Ni>9<9_^=><S|5ݷz7Zk ֱٟ[ŭr*ژh#sCrz8cCT/P5KVFGf K[sZ4_IzqUB (q?xܰW-,pғ{ nm]]/Ub8#f4W%gNC.l'A9Q?qiAs0+'^h'ѵs}"NmF`GKnߚo:b:Њb[e%Da'V"--T^J6(;x .Ʋކ)$jY[ +7q*2IA0kYbh,˴*c%t8q5A˒;薇\>•S8`mן|:BV MI~+>Ι9&] n\"esO>-2}pP^+Fª\$,j *q#jDP=lힸwż?N5 8EgEn+J:Uk) @ ;v6rzEohzmp 3 2Zw_ Dq!gKqǹQ 16~R -.<BV͏Uy#SKL)KNMN9 t3e&FK+]Eh{Ld< =@=y[R,8Y?jm8MӜN7NoLpp<am[j N wm_~]yo8h^ diE| 7T{S:kTpx{`0yiE0e[v͕̥Kg ,rk9W)A4yxڶ|-cqu󯘼3sqE[FzA2fѧs$1>&a );`BF?$2gҢRp@Ha ` ,qfo1ZS_^lA!_B4My:bVLj?||!$C6]kђeu! @tbKAIcBJjhikwQ0 !r,R2+ʣ m'rEtx$QO膻ǗkXXC/<*i=@ oJE,a1͙wWnyl ԫr[ϖ U6˝kI9n(ܑmmtE`5]gh^2t0 E? č!*?&J-9jSBKV I {j\C}ϼ7.tk+]20fb€I.]WvPtVd;v0Q H{ jx; ݸ)_˒pn'*ڛΖN <=Mx#eqkJw#\8fjId` d@ƜWD4ӫٷKxXP-Ĥ^cr=<.e^~O?O~׳eJ}Rղ|7^Z{!Wu^뵲<ڲ 0yhgU'jTl>/m4ס,pHW)=xiYRR8m\ *"HQuMs-ld#Іʰ.UABk3PmA$t:bm QAj!XN6)M>j͡9*&ńϚs2>^;)Z{gwc4-_늧ZL'\!U캝Ť aeǽw!7Di^)1AE:!($f jrhYPX<5EID GZkRJ]*&?y[ C[l<^Y9KJU!^DBZ1Q>6W[):&DAxOA< ) 5hKw$3XQ(j"էُ"Q*YN0oWmq:yҊ>y)^nݐ_}_–J,AQ-ST .uU]' _T-u;:\tD\ZGݽ" T+%[7p~[Ks7z |&8Q)*d$51*CH3au]`=#If)eWM=gA^t^3G.{Vr\Fz-n݋dw@[E W]UԠ)tFМFHYP&׭}s;!>b>x::I a>Np8Ll8iN^QG8"wW[^AqnyCyP1):]$ĵ8?_ĥDD6D]%6"C\!Hk~Ĉ'v/z*o))vc;U 2 %2wf{E;@ښgIn^e;ȧ7Hiz\^s]/C尞k_Y <3pTiz~z\>5,-"xM]GW/ ¼[l8s+Mr).VnQ2 _~Q<= $12c%p)a @4(.uDh:2H9ZW͸ˢޅW չ:YpZ"gX&G oI1fW ^ ѢM߸B;6dpwNyIAq .2TAZ;o뭭7k#Lt|e2:Na>4 4̯"zeT9s[UPeoX1yi8&13yH;Ş[ɗP ;lI4BLJ3*k)xa[酞Չ :cQG0! Qeيj[ K-ߎ[CӒݿ8ן s.-퀾Seع.RHӶE,e:Y/_a6N Bby:0"wi &|߾|6'K%N[^ñm-T2mO3s:"9W:eYګ?( v)`2`&>$ v~q/͢0GHԞkD B\O7 _4[lwTDH"OGmsOJ?y%X?rnh&OlOB}kA9^bW&|jtYVud0CL4B{fd+1/LZʪ- _r}ܚTb 0;POegΜ]*_QӵHY+9k]\~vRr0_{$H?TIƗ&Bt$iïA;yʶNZw֫et`r5RK1r-KDrӴ&|دc_Y[wVS]M>ð!]<kUbE$ȹӟ6}KPOx]Cb^};\oZ>}XĩAzOONxU4g=rSiRyd64 @5)fǜ~k)@c r[s:|Hj,XEgIJ*Հ-(1lDMeÝ*Nܤ GP#ԯUt+-n,]i Ղ7נǎ*<Ģkt ja{"1DŖ>=ɀR]V]Wbfi^Ks 3 VE[ϼC_K4mpLP ͛[+gBtM $-`;K6o^ ǣ6ODGX\g</.Jօ7% ui˵u mK[*}]~qLJHŇuktWQJo KT{wbmC<-t %ZKR@`)ټ\^ueǸ~E,O0K/H:+x%4z%ZT4D_tZ .+Y W>H<$;M)aWtXMC#uXrf_g\.YC7Nr+;wL\WLJTґs$3֐PY(}JޕW~uqHZc|vgR:[>(g#j^{WA4qS;7VȲ\Gk}\k3q\e3g CtL1urZKXX84\bnkp*ӋXhmm6C}>n! }$l`mgߜ˰>LF DĮiy';-6`@3"M SpO&Jؐ7Cwm//Cw+nssCYjsɱ)ˏؿSu ܧX`S]2+z91">oց\j {9ƠPxYWMH^̪G-aŨn4~Zafe(LnZ2-QTgiu]4ϸ/ӠBJߞ*K]Y 摰}akU3">[u@ԁGOQq23%zRL+~&j ^ šK]Pb0r )tuñF,'N{\i <:S]0Mj[nH] rxG5a~RTnJJT wBA8W;]1^]]^;$|yC+[댡!۠:g` M23 U iPɂS:0}J׼(_qbQ<.O9N7 ]9Hۥ2C-r]ӕL!0y旐(<Ȥ٤Z:!Ma°[476Dٺ\>&GQRL ncnZӗkVz#캰 bs[#)Fni"> \?`K/ )C`\ʤou `MCʵA1{$ˎ nEZ9(6WɦM^SWCrU ;H^ G#NY!y5-ψv{$h(wnռEH\!|D+2",䛑o%IGyZ݄]W*m d|Peo3Cmu:Nc{ہ s?Jҥ:\bvu{C(opːZR Q^ϗSY{^a^\WBo|8@B\a,ES.0{Jm>(|' i _]VHm?phgm5aǗ.4}ocF p^1 r$myYC}<6w[{땐̵K\j; }G*|>_ MYnVDŽ:wZyԈcl A>Oß_cN;uWVjL$-!8[59Ξ.'!_8Z-ߌV[ HD,]]f.iӏv zmS9s5}%H31 9Ġ VpǛ(Jn1K4deF0]AU -l̸lț,,:ݙ2*{Rwŏr- 6r&ΈC+45aUvxv]̓p!~j9Y߂ᦃT E䞻<UROnyof c~}}S[/o!+uC;=I Nm?`l4E<,BrRWMuY./߭EiY}׭:P.%4⬙d:!ԥ7|(yXƱ7tL)@)̋xjH8 CRO\ӯ/o/kr 3n} P--|$~=|}Zf q X}iBw8bFK 6M>_{tLJcMyE׶HLj\+P A^j q' *F;AC!49CpLyr~ E[|P\`15=ףB彛8EXav٤v/`75 @,ZDkEQCl F VG "tN㈦hd^x_]e^/~8ZTw)$LjӑZc*@b[^a3rmp,eXh( r?֓3#V&9,6 OSc|C֧e*Q~CJPo}Sa1QtXWeə}}y^)/7'uua^P̫+pYL\[tt>A *)]"G(>o6?y@]UK6sd(ݷ"j8HGb7aa]YT%2f]csm|'h\@h#&qi3Sqv ףڢBıD݂MDaT A{(TL*q6ƀ(`I,ܑQ`0G[lkL_8O)gy"iKQ4ʌ^VKrZ^B*xpg#tWm(y(- M!TYoli5 Ap$bEn: 6XLX{ "xL-8$ |i`[3Y%k>F6ʒhh;8DCd]w}O/VQ#X,>pw Q)C.47M2WTKB=A6 #NVXH`EL~5$oɓ )*o{aVn~uV{ Bj2ڜt|wv{}+t {.ew&~!/Ι9_zrY.UeCkIIOfQ[ uANaXDzypx^Hë1vmby`L*F* Z1?ڰ+l`AMI>DK^:-$i\VQ޽./eCm렶,o(P<({/ve q-@Ӛ*',>_/Oo3lIlyh0Dm_S'+0% { -%HͨS?/<ou~?Ip\kp|0 cGRNnnrs+)ԗܫH>9+fjs~]9'aa[3Ţ 5 !i̓Bi8u-P=VeJϓ }cdi'@.?,{f*c!LZgx*Ō1UR:a幋;kKo'Zٕ]c?6Nἕm,i2ʋ-uNt8y$GDZm7SHY)NObD\U= { VebyE|AZd5= k o|J.qh]4%nE9v蛚ۜ;: n2ce90"4Q*e7ʔu7ݳ ר7<ݟ:Ђ RnSi$҉n̴`OKobFVڵ[>tA$<3"aPj@^ nВ,bbkUvn}񃯸xn@ ScqV nl"^R/<߸ev1%4/|dIdSBO(Y@MM41R "Nsyi88]Kimq:CCMCM}cqrڶY'VѸA/S1FB-U#o|73SVt]׶7 w`1DwdX4cj >PsQgO圂i]N\)GHU/_lĢ*mqtQtj!UH(\iU;02Q_0}{{C0"tKtqY ^󃨷y]=Vlr2X_bD8 ZGQX:@ķeSaTd~DLnAzwqy%[[4A f&!ԕ22,4&e=?| z켍+qZ:#Vgֱƶ{-_˗OZzιO7CKCRHHJ46Bx ;< jjlRǠ Id1񫯟}:?}m C7Ix DpR*9bN;haÂO@󛭗yreZ>|Kkj)l#_}zK8]r&Veְ"U™׊$9P^hTMBSth7Mi7s*XXra*Ҕ Iacn\-GNVSI`Raك],][D,1<I&Q2|GщX͕ٔ.jxC۽ Vb]j/n&P7+c`4^P#K 3M2} a^ $Xu`6"g3N1YA!L/ez=lkI3 LA|j\Q{kvWo4XM:L²/9W}"?%9yٕViҨYGSK"C3/{(x |sSSCx)&š”ږaS߆<Յ s{*ƶyr5)-ӱ0=8 d˝{XP?qMtgccrhB3f5o<&a+ I\̄$)s0~~~v6Ƶ,o9T)]1_r˓ JC`r#햁E7;FJL)|t<2Ldzו-{*LmU(eSKϜʍ%\LaStn*S82z?P 2kAdO2#^azg UVA4ܨ +^WۊiP^p.atElTG]sX-Q8(gmL#G0] zΡ/DpG LsJ^,'.r/;!H U ~ɸvp1aT.&MOre`{Z+HO,ofSu]eY%gՎS #W-y9~q7${Vzʑ#A#I]]`d&kJ\i8ç%r}ޚ\i=W|"+t{xDƌM1O] #t#qdZx^~/߽\{G9q&W [9L]Nna"MO E59gz>Tۧ_풷h<(B/V$9A~^ ǡTLO.cywt@ [`i^L0+!bCy~|e}yՖk/kCf;n&؁McL0,]6lСnPg15۾^* !c)x r\{  0e'[.PaDž|x/ּ!`ՊLTwu=ǰ,Uo_} n׊6fCpQ[7@G EZ#`:n&$_JٽB%Oτy IyU)S]rn ]Oax0`)JDVRrXXȢӴ"A,+@A8}4&Nxx<Jw_"`ONLciڛ%ɃA+ yσaZj,`6i-]6>3z_j=X xjHJHY{Nl3[2@. $%jvEuDB g ^+Χ *-J1qUQ4V H8}.@**y?Ywԣ"|jdc7IvL["i1{2H1xGܞFVOZ XnZm٤ 1cmu~r17o^FzF4Cܡ,"n[[m_./Ka]1&u-4-녽^4cjNTѬsm⭠ts+Ai?Z6d[Uǔ &zvF?K6nT,D[._~M6+ߌ[8C{\}!R_[M(l@R[S /0ϧg0ֶfإ257$׺qK o2 {j`xlϩ[oǚ6mAtyb6K+.Fx[J4^?^rr޶n/a#Cs E,ut|}˂L弌u : %O{nqN'}D JBE#LR}//}9uGi,1B~}DpQ7@_,7dܣ ZQZ+0徇rS}cфC0ykRәCwM[\1'T_>$ܽvoJ v-bVM-/ K͢؆Ap0,4s &+hAYRr>/?;_9ԇ+40Aq'UQ`pC$9漒o7EKR$`c~di -&9[m YQȹ.K٥>sԇ].(v֮d P?3[S5brmV2ߞ5L6]T^Ke-?_G$W;&Qc>s o7)ϕH%d+y쿮uuskU-t!a,@W -cDp@\)ٚw^ɟ^=K^A8)yDTؖG' #FE#pqavR:{m0>SeqmK]}i!LY)-7VZS( mSwVsjpwǡf !d@znqVgb*,Hibf܊*B6N ,%VME·2BjV!'{3 [m`HoU|9X] =gNb!L@5=LyEZkY,s+`.c}+&~TcO;쯱jGm+2d0C"^9psO `M!R‚ӊ6a֟;n2΀* e W;4J߽Km{bo ux_ANϤ5̗)&TDwUKb(f\!j[cU h>Qmoo_|ocHZ!&q%<ϹӵB9}]8>N9W>>N:мzFO6 :ݘ=Oߜ|r$TPzi&VJyj}o6Jw=Z;( ,+d1!_rFz.avsd\XED Re܇gSsj]Htl>Mߍ#|MkWsk\ʋqx b8,?p \xԡC\//uNۡn@se!fJX6W)DGCt9uɽ56.E{*AnZQ:XdRu @u=>uRDTp8lx=?oQH-'Ba"kM0lKse#[csjAu]ˉQDU-[o(mO"xE`:i7mzc.G-Q?I֫Y,5e^oAu95f{ct8= Pm`R_HseQJZm ENJcD<4)b,jgXFm*,`>Zx,E| t=8͛区Bd!l4oDRy]sHi* $_<“-pha>Lkڊ1.m9D`׼^ 9Attd ۪۟ykx] NϢe#m @ȝx44@u9DuS֞*>.@C&zZ>&usXRԔ"}/gIV28lGaU'\'(J rYGZGVm9 Рd9}mS@%R=?]5webUUf*&nܛ)>U4mwPf$⍾s;85NIx[GE&ZY36 To-{SMqY%@=W:k|71Ny&fh(D,ʨES,HY,WzYJv9g>/l\6hD~WG(<}`7$41Y>hjق3=Kp)[1K>:2qz9 ;⭦.UZv+֮%12.ߵWveaW,p"Ưq=5`80x?Rdžԍ^7*|Kɫڲ,9e"heqCTJEmX~]zcVQܟ M8{/yn}YL.eEGK;ɝO+($[ eu3wg(=Ci7/ xjpmXz8 =TPգgY0hvZPzgɷjS|\>?+趲n*-6Dis9`l5?OMd /Ǚ|TTjR2W7Ji(㖷ص u^؏6E/ek/q~x/}|Đn'[c&+=x,IA+pxޮQ,vZCӵƭu&1r<ʝF L4^{ף޶tQ T(p5[XQ2/fcgޔpm-}K޵/DeCр8VLTny* D]f"T)o14׃قBpp&mD%Z÷Ztjrtk`n:IJ a}2S|͝K=LLx;"`k~t]]!  7GZ8*HX=oCoWQGZv409NZ%|Cgo5]ݔ70`vfbԞ7k#s[|ipޑD +`?R+͇cVPB\ O0Ha8[+veb1 Ǫa5 ,yiEHVe]{v$`"胁H(vghŔ\AP&XpcRr^ %N;I.<*2|νtc¸)pWRXc!U E#T7 K%KJhk*17R4-=z3D`PEWRMS|M))ͯ?*+.=E,+l"'e^'UTF跖- mֱe^&% ^7_5%GJN⡄ WC&4t+wDiXC HpG,+y5+"ZӒHWKztx`[1@p4AT#]ERyf9M00_0V/yma[%l.JVr ,nn2@ebPi= if|m-&/\H ߲ƪi);6ONǡ^  S $SCW; j懭syrE ZeHi"*M[G0-Z*o;m Mmuj7ϺPe5Uy䗅=b, XVW\q\z4?*5;! (l(>GpSNb!:_3ǯOܧա15n\Z̊k?]YIU臖_1٩˩0OGg[SJC8"VeΊTHTDq2(Oô2%_aimńWz\0l7a,$eC,aLMi- H4B`Vf5a,|+LR.nkm+|8gېO橊}CGּUJ554,;:o'rIEd0531An5yVXziQͶ¦dKAdn> 2ir(DEJ޳Ҩa4Uʲ)Ql6N*J6t>?}{ C:h.ثqT* 쌻 YxH gşYu8M:IZD[FUHn dHWw qF3Mۣn0 *A2b㱃qbe͙QbZI.WTȟ ,. 7K2Ŷ#V|DIp9 pcV1HQk&7=SJ@J'A"r,-ninc  A}5 7A4֒ *vnc``"Q*-?S-Q{0*hIZd(۴3n؂ *:G'BɼCʇ: $/}c+D0&N9` O`td=PM,a['卫hD&DAsC5ySU*dkUTC1*C0GΖ.Jl8'.yA Tơ ,v(ݺ@O8H`Sܰ 䩍[nA1-l"d6&)xB!/%7U5<ܙc!雙[MaKjA팁z2ehXkfZꩼthnpZRp(&$Ci}cerՈ UaS^l$ Gf5D6 K-#7%4=6<Јolbf/5ǹM*Υ2%¬x7d>H D'7Jlr m_p)յ[ _"ZPT2H.^;)IJLg#L_|\<;2bGf\S8ZI,tjCdH67y?ͣRba~)p9M~O/׍:uP Z4y.\ߠJj5*˹Kp ZF1芯8`ی5\ ;iL:C/dۜ-L7{х7au!ww46!6|-y_9*Q_6?N4y~<}\j]sg+j밄-nM Q%pWpZukf{=;6˼F+S\kuY!M5N|-׺U²GH{,uov9+ q[pr>>H]F+K 2aA:xFC?H9G% WۚbD}\$ѥnl:0}(isj1N%Ks!z7(Tvh7qN.v9;J0%ΜeY"pʝ~{gAZX#dfڇ4Oj>z6❹R rwjZ׬y%cU3m7Nu-sR<dD3O) ܢh#{vե tyO]X$MG; HFF#+7Z#}srA`1T7rS44Cq(+-3DD@ԗs+ˑri6}^+EUMe^(=eўe #c QF^ޚJ N8{mFEÈvz|TEǦr1/}+qHZJ7\{W~ƙECm}IK%p8r˽X9[rahzx`3fzD2ZZ$󴠑crvӞ C íh}~)[KPZL)uOJE؟ב~J3x0M:Ak._[Y:j. ѱONմᖔeHʪAZhFȈ"`tz8Hw?7kFcVq!;Xehz=LmxUl[erCk\q; -t}rX<[?ys"Y8(С8$37Zn:L?GO_5M%Dh8M5}f6vVں=;V@X4ID\j",$8߬,0 {X^"]S֥B$:EO"Al}t8o[F.ȠDcNR5Hvs! ;]h;'ύE!n#p3Ta-;ؑ߱m~m A0M H#.:{ݹK-1Z9:jؖeN%"'纤ʂQXkY^||7vL. QBos#G6"pfg|*_Tk8,  p)|??g:b:s+Wx(S;;#d19+eύf$1]yN*mUr3KǵozFK~4}+eb+..k@l@Y3&ZMX濕ЅEjes<\6bEe1H:[SS|a;@Yra_$M#u.-loJ2 waa%UynTjdzY/K}ᥗelw)]K!M**¢L(쇻%0YUHp.[V{jպ 0qN8_? kS`6Rpn󰝯VA5-(NnEHԼc+ ajAsC.') ?)k{7k_PU}x5SViq=9?=Rg !pUzJ! FcLj폿c$ #,&r%FD3LkH-3~;GU& `%_rRB"W[YGŻy}R ކ4OG+N3fr04\bH vr-nkd$&vjHִJ%ɸr->&L$d&4Ǽ6'Ǹe5Z^[ɏĖ{GKa7E4 (݊CsY {2U~84)AhXV Bl;坉!yьLMŇffB^91ז i =ݐ#5=~9(;&-m-K/.AmoXCXEE*wNFYCdwAȝRc+`8#JlE/u:ܧ!Ǿ3arܳW]Er޷ԇ҇ gU93+/pZ븕܉(Guw0Ҁb.Ȝl}B^ :^ vndV$@ utG[xLY$u\ +9A7so& $;TG>BuXs;Z`X7q: [ݢ] |y- V)ErY=Vƞہ[8iwlSN#BhYꕫH+zYi3b\oؤS!t3]P|cu!>Ͼ8/lHM7PSAF"ϞӳMt)ͭf!VzT4}QJ/Rm4%K>(.A5!;YLF^q&ROoo1 Pd}(4m>VL1xtZvK;э݈T8$ ЫsTUԫ_ T&][AkAnHmt^oeeD۴ KyɭR0\)lYgֲvz+4%6|}5%s^>ϙȂd[|Zm6 k tGE>7=4cw AUTLyCUl;d+gr ;~'7++wW"njHσPxVVA#l"OGXγ暯5ȨYaMC,˽/C>9"NCA]CWES}C*e\K}wuqO5almYv78Ezw5=qB rg{м܊%o$޵Gfz?,3@#Si5ߤ^ 1{c~q|T&.4M5y<͑HQ%\g3jΆ ?ǿ>]Kw0*Z ջ\FYenWYGv %_c;ے6+11o~t/?U~7ye 9@s% 7Ckm%M6_}Ϯϑ4 ys`TWEV=SF}*92r4tՍ[Q_^CC;F M~6y,?;mH4-xdn>TFJ8R[[n=rٮO֮y?YvԊ f &Ҽ-.y U5 @A ܕbz"M,xL>4Y{9s#/?/c2HTD0. >vGsV[. 2.uIܚUj(e28OqdleSmhwߒ?'mx F 4GD]O;( .\*Wfy'UͱzǞ6upRP sSjl=>w1p_zy^z.ya81C^Jખ@"Hj\V{iJ΃l d|4uöF2j"\P(MOTOY> f(iK_P v+|kW2lHj5sl7Sb2øFu q+} ӟ_n(V^'eiõ*>B ݺŔͲ#^KBCwdiw+ `f!(SusY8}{g؈Wbْ-IyU)bh!7QV1ʵï?㘎>xI0 nXmRҷ\@ur(SN=<8a0W""W1L'kq ~sq*@nl=?G:SL!h1XwS%6 7M[ho8PewEvV8{RVO$`:رw:Jɏc_o.ড়/1^5ݾ_=E+xUQ+%j4;naH !I1cB}E\/C8Z5kj!j[U}GAzy1hk@4uBؔnՀ8VΜfnWt/l7^_YˀHDh-3|8EH*pRBs-_j"u-.Rr ⶰAϰo֕ƒjg7K~!6+dm)`-x4ys4hömy6@V]0$}m7u '8ݽhy+&DgF~6RR2[!g⓳)}0(`WjCf=0#U‘f?x,l{ZCcݪf0^zNRUMFUKy xw7"d,,A}|؀bZex&bpBg{\IZh쵹E!oF΢@FVw3EBҏ>kcUƛ D,-:A6b_?š{ }i qt5v 45%)5vKmNyqڟk8O\ۊ />؆X?}"I]7v1 وNp)Ps>r}.řz.HUּ2GŪ$8u†2)]UkZOd5M.yN-ޙsKf^pF2'oEKi8z;&2-21ո]1ܚqUf_)6~,9})s|ܗSˠhV Xl6ڞ!c8L'M;)pcPC/VF"BCCцc].[Q9e v/ŨDf gq`DU !wQ{ Ǝ`<&>cđ.SJY{4` g&t9JY{G(|8XBELEY$x,v[:&9x x2+&l͘g<̓ 6U[:nlBy,M<:zgfDkNj9䋣 d2dѥ98OW8Ic,[^;)Ie˲>qI77l_689՗o~~yY.j\lwF{ug>wxXQEPYbhVn"|ȶENG2hx[Ɣ*%˸]-cԩv{N`ߖǗWǗׯݼm0MqJu3 }&u>`)Le:.Hx,Eaʖ\4EɴfbWig FKKYV ] @Ky,ƪI*<<.hhȻFϪ C!(USsѼ#͉< (AOjxM!d[62hN,\(ઙat嵍 LU.n {ڂ=pÅli$:WhL tjCU zsb",=n>S "O9nxɌvs\˺ja8Б$(G4Z(kzgNc׃42dQYƚEZ7o7vϜz/Vև}:JdpZզ|8^~˯K MXjRɻ@ ?xXbvckwR ^οƴ-.xO.%2s97ʼn֠sr WjWQExb!Fwk+o/9^}$i6.Ͷ*c*@5.ɏļ[C|hgm\{C"3ayJ( \NvQH3It9B<~" c譂X/s1w4|e't'9(Zmh1J 2M_EB{~!P^o9,-\nVtAqNNga6hY(^xb}ȆSs$YV+?JouF)U5u] 5r~uaJ"~XmQ6Cs1N4=g@(޼|,4kAM>ֵo^tfI>WVÏ_`m97ܷ))I1Njl>!"x5n3OvfL4R^ JQy$Qs89h؛D@wjF)1fcMc7E;{5Yp!ęhCi[ 1J3bG.To L*8j۸hX F!m;; 7 o6r=;CZn0Zp [u<_mI/#.{a,+ӯv5!)0x ݍX)Tr8 1u ]S^a&p/\MEcZ=C(4\w* -D:CZf@0>SϚ8Gp;!:k)__:")z6:nTbE`-4 ǘ5>wuWx5IHvM0}@:u;ơ9rStb0Yr.ϙ[^z2$F_ Pڡ>>ϧOيrRJ duLb mlVFmB^R~/0v&4o:9Ͷ14𜰖QuEmzkP)tA+I>XSQuIPM| kY LīܚwИNba"' MZ.6>+x|=9H,`35ő#AzDUSI|ʃ O}rvF=9D.qݒ6w 70|={e{o6* ѝf޹D$Z=,Y/+럊3իWNWHIޜ*Vve%!t]S$-MD*Jr8ZtݦgC(;D:כsn"cFPa9PxKuẊ7д%Bأg.|yHS:w}[r8RÖoh֜ћZ9.Md3w/=!QJ 8nUHYY^'?,fsZ1axkRI os)u^|sz3jc>O[oXiYQ:sFij > $1i\R=-!Rbn/IKJ^B ĖUCl#TjwCmlK^sHGTm!6{0[3?*>Bof Sސ;~XBN障CB$xܰmu߁|Ivhz0螅p$cX趫*+jqMA$ lfmQuӃ XȪ^Q!uNp?:, 2bsӋOaPĪ֟Tkkg,MQ<*wCG$}:jS'- Fxz\1=x=״~N^X.l;N%|6(y~/OOmN&9:ݱ6dUM&r 3q[x3LTi!3fƧ; WjkeVY6(ZxtfiguL_o pCN"*&+.'`uvQ* \Q RՅUFc/@TGu5Vmdk1XE0B< kJ,P%x]S-u-l*3 2yl$}D4ZTn\&.;F-&E A߰3Y8 jU}#^2@ˊUc눕2ZU9 ;_tl5 G*.n,XqPAB.+z_Ҁ$ot߻{ 31,5u6}KKC8<8Ճ.=^Oǿ/?_y3MgDȺ*8a%Գ"G8&Vd~PG;ݠE'8Ea7w%[8wpP=њ5 1LG\dKSw o9 ۉH@!c7=c$Hy"U) |>ZN1tGcohx_^;f+J\1@*y=n2R !1kJ,FO PUɁ\R:~dY߭XBB 6dMj+͖, ^b> {_WR%I`ؤhuֶ6Q{5gvࢉ>MiJX4u>2$At +g2E7i*23R%h ^6?NҮ!72%Kln~e=NKxySr6B8ePrWT|$jqcb\m2p*7?,@]e,p ZL`aNi߶Ҿ>/}G^}o~p)e1[-D;I;QHCeݷ ֬ nӸ% 0@d4`:Eh$/%R->>_p^}+Y>Տn UdOx<^1JVhes%+=|Ht~rq­lq: nV†BIe0=(~-Rˡ$ fU*=E*+ozb9yg[r]l9IP8ZoiϼKFRr?'L$ݤJ* :FKi2 P2pnM"ݤ|:jw?|g/ m@f׷е76bE|tH1wI$Zbm(C  ف*΁!ܳ,~3NX!ŒI7;@S-`Ro`[IȊa"U3ds 81)Ns^~۟˟??>w_ͫUc3P׵ ~y1Zw=6n,!K[Ma&[aG8QzuɏRŜ(Ѝ\4<Wu#PM][~#Li:ix)VyetM6l1+wY3'%NȔba]Mh|'cƐA-kQ!nҹ-$'yX[ߴcU!!(1Sv ;:8{v"FAuK/mK`@>uƺ g؜wCAs1Λ&"m#"dY`46 33FKl[/6#M]t蠽!25Nۇ YlQeE5odl>ݔ:tG1S1Rc)v*[$.mr{ |/}L$FbqCjnoe1fveD<>)M< 6׎Rqy^6N0TkkUe=0ATJa󦶋Msِ=;bꄻT &YVvֿ?~O^a3\H.qxmPWL~R[^dOcg @d'O-'E7 Gd 9s.Z۾$ʲqzDwPh4rPRfTaAt#<}'j6)cFѷʹo4Q-| %6,G(RRp9Ǭ[)X$z@BV].Y; p 3{N~jVHE+nw#MN\}lANuEgj:FCl>+ œ׫e( *J|50qxޣ/֞,Q=ށ{yo!\6b9ڶ/7PD#5|~Ո9[-kϿ? 9}bt<|ߩ~ DŽ+C-χ-K *kEW>e2Q1w'[eDou0 l<;dwh>O?=Mi^ewMY1,qwf֮Yj[TU4P+I΃=: OBJ1ՊmjR%;Mq!}_k -"ept9M,elD7yKfہP02N\ulc.WG|)so|OpulтSt=V)臲 uf3&da<) f+^$,z_=h{o% k(az!zyim7$pc:.9{`ywxXвdTi#|Di3ԛÕ2l2L-QHp*W2 a46PAE(կjRt鸇t o,~ӟ||\PWo_Yt ?ś|{>!S7 (`=D8C f"j==axO?lA0{uNquht7m&>mÛm|}~/xףCOXf7U2 gFQ W04HAyYR+dU-y1qdpk `;DҪbV[ Q8YPcMK.̅+h!heʦe<u_l,*7VL6g!Y~@}ģXXQf<{e 鋜M00TbnQoU@m!T&Fo*\cƌ;CPc4uLr.l¹ַC>17ͽ+72GmD( C%z2z O,m̐i:-ŖjNu]EkW3rV'7ݾ"vh`g )Rӱ.<9j;MAZLCMCAʴ ԟt>(3VL[726&2*@4'Ss^Ia 0ʗHUY '8Fn'D`v y1 _ 28a'F ~ouLZzaK1wkq }@ :Wm0#`N^Uq#Nu3;5.lڞ补TWK G$\{_0ռ+UC9Z͞ 3͉D]G2Z8|}go^1ׇ|㏇0~NqzB'5yta~KE)7#esp37Q{30du$;~q;!Z cwoyN#&H;U+?8M|o~Mxs4 m_n\^ZZYX\#-7ͭk`T"~?د0C\gۜ6VCS0~p\ȔtN{ro/_XLn$NC ZD w-!t Z\W1aҀægrbm2&0}d4YּREuo̖w8,WTcA;!jʔgeX2(@R:|;cny]*[K*JZaBčw@94C M+9k!2fM]inRGJo,ĂxL?Uv10MsN1RK⁒1͇A* n=+_v[2les[ǎ)K&6IWa01ϔ@$Z umsIaJHr*aPի}cdnP=q)벘<VD%Zh htr.mE@r-)qg!IO,l$Ckaċ'U=Ma䃡ezllokSHG6z8 Hc`+cTn(2("k= $.5 =Q;@7Sm{ae*602pSjڰ'}Q] w%œݫ;cl L%4VZva,AUi-CH<CBmЬgTUګg7N#62j9 A\ʹwq2!57^ N59ڝ2<ԃ^'$"}y`~ەC|)̯'nhϟR囼x.AUԬPiqk,64k0q㫸%x0ēR q[ݫak=cc2eIۥ3擛Q@ֽ#ɣok *t0"n;pT/{ 8F u7gs2(}>[N >Z-vM4.(RTx.e[-iPz _֞(~>^O!xpW? m8 =7OEDpq~Zeo4t쓅5=\F F,Y̑lsՈMU"qVy0iΕ dQԘĀa nVxtO4T-L۪y|bL*#{Shvm|qo'J ]pvP6¥%urPN7Ju75gњG# C)Bܶ©ZɝA6/+0FaB\Wi[7zf\ŝΚg1 QR GgQ=v ՜"(q;&c+1z~hޣzdfŤbQl?8JArTUtR5GT%yɗx #{S[3kn[ ʰR{DJ>zI^WPyӴ0V?Yp\pgQW.a _!s/\8>Dtw-OÀ*qtc[tp84y5}A ;KܟƇiRP yeye]:el@ ͽY?]m S6`9oCwć h,! X(F;=(30;%ʵt(Z$n^ib lg##T3;) J`ޕ'̑aVy6d7-[ ΉWl>n1ݻ{һAߤ 0]`߄;YN3fRږ:Rˈ3Ɠ3YڂxT)\J'CJDI&%eYw/u~iۥ0k[ڿ=ls[Ky?D`Bp-["9PC+,;ºPA5~[ؕZ,@ƞmQ.W,16u9IĔIDTXߵǛMN/#+hn,1ލxe4Wy¶7MuUd7εV'= v^h:CiZnFL[ۂ$B.`IU 5 "d Qf934 ^k{Il$#~=a+۲n؈r8@S3`fjVP6Dg] &7:(p(a{RwH= b9Rs+Trjݪ4^ƹH*IHѮ )1ٸoSty{7OGq;辌+Ek ƪTX:Bʓƒ]Uqî@H5˘6&foK7kmy{!y|HӬ4L/Ӹ1 _}񡵾\.W?֥~HͅꦲUdHyL)rE"cg1ɔ(fBZ@uU.^тKH@U*s:"xSN--\;Z3vZ3iăQ Ybk[Йeka~[- |k]cpf5gzH8Ѻ* 9P^y/xL^j5 &bQ+ #C2|#zJsSz*Zm{5bAY1;dHy4ZR'LJ#dawj'G7SHb"8JzF(Mc v?$:\P[ ˶$ذr;.L5STHBmY뺢A~skS*V`lI8T\QV^)+mZH^X\fzXF+Od=^@`-q\6竃7x|S9J]2a'>_riV\6wHGe\ra&`:o:y,PwCɌB_C-n=v#a fY-g!|(Tتnat4#"A0+pÕp2q=hY tZ'(uRھc!B:;%dѹzMӡoXgK˸[Q\qGFz͏ ?$A"8-o}N~D?g+Ү 2a$IU DGl67($Tjy)wGf\ u4>[efU9(cc?MgQּP Nֈ&leIь~*`U*Ak̿UAE#c(D qrdȷ Y4a_7mY)܉+50 oAȤZqWf*A,'t&O|ŕ MOaɍ𨄔n YDm <+a?J5:_Řh}>t@m W|ӫW.ӫi:~f>h^=Swz=?J4/RM}W*7&wڦP_RoHss"`; /];*:ke'g !qbk-E\.r)ZfQs밗G'2?q5qwj^Q"{~Qh-k[ki nϿ%DM'ɁWθb-LGZ-`y9o3nߎ>NJF)0YE%,qݖM||*d&Y 6s-uJƔdp3X^+aѧ\2C( ztD|qߕ#;7g$0Q^HT"r%^wj]-Hy ԈqiI^@^W XԐDi7ϩ HN]$ֺސ)1jy"f["Z{wqVEL6(I(F{fy4Nn?~?RyݤY}L I;vO9,5񲥜Y6Uf̥BZ!~;E { ^_ۼ(N+Q2^Q?Ҝi]i%Kw%0r|sKpq( )8t.na;>:[cJ۪̒[vv@l[lky e,S^J[o\@,!H@Γ$B%g E8,kwaLĴGruUh28NK<9Jyk~x7R(M۵o?{*NxGc_;twR=Si˗eL,viQJ2Sɵf0XRȌ!6xvد6=ށh||{>Nnȡ${Pe,跏4i?{ָ<z `]t;݇&27e{fy{y16 _p#i2=)p< Eq_Fd[whN.jE\uD .f?MJ ?f, ?rޖ}|d7L. ֙pcm!l&V]V4ڒAgo Sn}M!6R$%!.MAz?_}9kWT_01hF7;t ژf o˯cN6B8 Bk{*Ě' Pa{Ꮄ'6@N1.=)431eo6_A b4rpM(l'Ci-#AXsF2jnmdɽwVUUG{$YNtZ[Q)*_L KXP]>l ͔%Qt\2 ^oJsԳNlƎB =>bpa?„d.q@zn͂'N1-CpJ}SGo]pfH3 ,fе6ӹx0AO?RI+uRxUfhtIj%v_QYˑeZpdyism]nL`2^Y`gC K-[.2 l~){z,xy72Ӹ߃u4v(fp1"(Zg% hh)vv `HǘB2{Q~ A:עa*L46vjXEqFBc'KItȔ|~-CCmER:|)âgCgzi2]MO7EV5UݒbY]ldTѧP-2.xԃf8@싟;p·p킪il^=ۄnƭn[{/TEQ|ِ]0c_'ǟMwY};3Ph.&ָ#G0j "g6eIhJɥ0U~.JĂx\~@'r6+`բvskgqd& EP=" b٘J&I*_ί<[=mCtp )`߾{x}|HũC"[>h)]bW\iKtbJMRȩU^Ȏ CuMĬ v]oZc0Z}S+e!o0~Wx4Z2K`~g0,qޮtNPuAORwk4оVdcCHN;U\&RZ4 +J9$gB @#3r}{A$AYJч8ݼr,[rB?`voU2#wu]p.ZH-+Y.Y'$ekխn}hhsS=;ɐgP:K Z_B"P@ZK $p.2+,k^5}F9lfiLܩqb 7XɶNP'b2h=6pըLե2_?ƞlL[%ܦ~}OcD"2`c%|dʗ= wم3⫠VOdcyBO"C/SjrpV]餷IߘV$8YRϚ,3ETaF@p,``w@ѝ46<栘ZDΈ]q˗όY0Ͼ&!LP^o\ĥ6lE}NJr^B bk>B7uib,[ȡifKZMP?Wk1~sDwtA<(ry&^Iɣ 0VV-YEqӮy?"nej؟i%sS`u|ReSmtu0+(S+a[JCѣfF"ϒp G)o<.R֚k1K9E|\WC<<̖{&oðd,w&l*w1`ju3;&_ Wc(}H3,'mWtusR~,tv AL^#xkgRѣ ;l7 V]*+W3 dSk|65Tt(vJ q {\3QM5oL!!6hh}zj`#F 5tKo Φ1iAl3Fv_ "׈]ƸL֦::h/mA۷wWXUcQ5+S-t\jïp|C\tN_m IŗdU?y8&kZyZ & E"YV}Vm3NG2FҢ_'cx$v,rICgkx>]^%ҧ42 -qW8Ѻ>&OX[;FVx,΋>wE8y&OJ'VAJLO&5r?ҖvH5s\Mml rzDpREnȋ,$jqLGz_EN!jhJ^gW_8NPY(:bXHٖ$(]~ZQɗ& ㍜OJ;a &u}&.G2O9y.n]R͖[ &Vߞ9y|}ivSt};J18v(Ӱ2R}|{o:lȪz$MHCO"w (~FZ͖9Hw1x2^1TA0^MWgC#8Bʙm m=~<4$i"FYfU٫vwE)| CER3u}_ʶ7m/9k#_~C 7g4e( m(}\G'N/и/.]B?{s :A(lOL ȣG_L !Ŗ1Rdv \Q)'Ӳjg8焷iv/wccq b:x(ZwM$JC.O?~[ ḁ_N@Ou4E 3FElW oft'sww@\!W30)=f\ r{#|*%?\cM{3Tyuj?r5U6R׺=ƀ1SmA6 גoo߾^ #(gAP⼮괁|wu]OR>Y5a׈kp_ 0Ei]p<7fYNlW>*l7ڒC7 q˛vP >;DJ .F. 3F8r7PQ=B\PI>Q Q뺞_< ƙ*DK `(4a-ȭ[Z;c4!2"]:{n6%0I !XpzEކ~ŏgӇK 赲|4:2 ͵(J7+gš'r;;y'̿_^?эH#ڬ~_yIbZ3#FTeYrȀu} ޷g!,N %\e2aJʨ jtM)(F n tۨgZ+Ų MnpO8҈1mkGDU'7G]<"uidV^dAw6jmL3[𙐽nxt-MI+$s!*4BvJ]XFB 'V)9% 6H]qe e=v(Qf'hhkfC8i:g(`!TŠ FmxRFŇL3\>;ZEz#R ^ Z(V[3?NT1]FJ`3NatA89㕉ҷnLiY@tا=-(t 4ont';g+W2pZ&Nܨ#٣1ޤ9TD$WH64!qYK1~6gshX0F\z<)v21A\SXb{]_Ƿ&<mس]--سŊ!><>%vtA.~/uƇԓӛlrF3Ic RKKÛ nSa|]<:0dsDOn R~+8H_Wb#ʕ-}G@.9҃-BK{F2o?~|]sli)8n| 2,-3co Ҝ3A*f[xrqՑ I 9ԂXաzk?dt#-[Ge;Q-"ݷφsLԫgq&/_Sƨ?$B UPzijG>ugGCQ죋uTQ^b9Y7y>Vo3מO_q-}ï-jg¹7ts>亩QgSj5 *1w>@]|m:VWOrZD_dwjmW'OKZw2ADiFrۇ& {nG)tM*)ݢo<&[kXՅ"V jfڋk Kƭ춴hChDƁx$y!P@x^|ߏIcHFKjPC5u>okUy+ӱn5M9E(~txȮ<dd2F*~R'.D%u O&C,Cud*b0!M^)%^.8K D`uI ؛'YQuAs驶y>]Y`7*[y#۪Xȹ53({q]2<5tbUz'Kӣ~b})*k+rջ5 q~=YF8zOsl1ndF$:0mo,X%`3 Jc^T &_9cZV;RHۈ$NH@ _o`0Wˀw[8;ܺ:uY]xx^;-l8/ 5]c(%ҍGȍS񐻀UkhtoLL^Y-twg҄QpeS#*l-Iқb*av$iR- i ޡX՘Ƿ?Ƿ׷v};6~,z3Ei(@5ضN Δ[& fZ0ufr ) 5_;<1%%J%`rX@_-#wK{w:h+MR |Í[I]@95+oo%I2v&dzͽ!љ4EA?D#!^Gί?>7(,%^N"lLDhx[?.a8n3گs6!irbK@\AAÉ*`אx.Ch4ҷ&$|KXj@C.x~ϦݓQhi^ϙ#! B6`"lU8l)֫:-oIRirؿK; 2Qp̵6S N#j>L/R-;K/ӷF>sV1jt *Zh n8eQeWh -T,}3GF+kAJ VY/ҧ!O1g.a|Ne1%1JNHΪħ/Џ_?OQHrSbiNaֹ̬G1Dւ 2ݰ;0/lW me8EP}ZUc4H.`6#V6nwDT䉛^{VUu~(o/d&.#7TꆗxHךq~X+! ͺŧ>r8Q F'9 yD؄\]\@ހJv/UQ}Ʊk_ أwEEKsI)Ԟƣ,\C9}/^/]KJ /v1 0 0Ȳ4 L5D ٽUH3ۏR ?bKK*ۂkwEdDydN27M^]͕UۗǣԺ5g7/֢ޜ3Ta qo+;H^ <H[_x\^]' /(VvG)\XLFBa ޺X(oBZ ɞaGYe1/:aa7\YIilO.|iYVI]҉Ɠk1J7`+qyETRHZG¼\F!e1KAǯA%X-Nkz"rI(3#ZM;jnjcl[EAeꏮwW/_?ɗڶzeN|{;\֙ aJ-t} ^%@atCVh7~ʔN&d1?YE /ehz "mc0әUyW+ffNE8],DvİdR[? ȱhdF6}`͕ ي87<3do7 {y@2 1 GT!<ͷ<>J6Htk ucufH!4evrUd־{Nqoi-.C}+`Yi$:B슒9;_2Q1RR j~I#APZ嵷b#z:i(p#Bgz6iE<=(WQlD|Q5 7 U?s)،H"j]b =`^}2oUAQU [GQ$A}\1;Wko嘙:[ucL8QYrGOa\쨶ywKޠJ :3usρV$UN'U^d.s)yiUTl7N(j աk4Z6;Dy_NTC)-Ȯ0:1n3M,{'1K')f^" nT9\~yxb+: #@.8dS )@Cw\U7Վ?%u:6{ ]ֹ3vVQjw{tpz[R{4 n#c4?85*mvrpq<e!7H4w;n>lX#V=#EEU}wZ9楹@ X؛TXݛxGs*'F<,4 vޒ%8eX_c*ISTyUzf:chR?=C!CU*c $1X w; nfyF[$j{~ T?O1!1^c΢z/ƶblj#k|Amma^@Su2{՗xpkMoS܅¶>PtcZh\]#ܧA@{\aa\k8 GtALm(Dµ'bɩÅѺs6>^3```Snjvm@(3[0 Xk@\?j#R=%Fհ :^P(Vk5׫P ])w;qz\ ({Z6Cs:Q\`xy:]m2 @Gf)Jf1WM4\$qW<͔Ni+iuUxRĸDIM<ߥPj(cz/{Qq_&p 51ZJq-ب!sg+]E@T?SZ |*yl؝6̥$!O.[4CM΃Frʢ{tȗC^'ySa3}x>SD]m\۶@SR DUv3h5Agmn_"{#Wc8#6wPcV,%v>)@N$: [8=aQq^ <%5Ŕ4BG'ŝ[ujkXgӘ^D DB|< ց#0)6d8$%5~ϯIbiI߀,}|:2#qJ>"lXok )hfD^=] dR2U#U wxrQ4Pzu7Ӥ2mD$xh}!gpT.A܎\rĈMfy5*;>+g,rF[PR1rR1T4\Vil9,Rai> ~m꺶\i XrMp=rbJRx¹s}r;Y̧R+S\`-@S90=xjDiL{>!s@' S w,Ͳy=;HhwNn8& RZ CLw`"c+/208ú u`&XZ*Ça'enwy<,RLX=qe{i;|v(5uLu^6F VO^,8 :e1(RFK"CZE j4H HXmF[55M_@o#$ \*[Np=7Lv3iOMKzBfLO-KBL  wüb9܌7yr;C.EN}|)c>/lG#w|wGfad n3L%p"#e/!{` J4PofGx|uڢ͓<.i&NF+-F[Oo)(aSah¬E<'&WAni ۄ?˾xp zZ79I:= g >Sy~ı:/ u0KxDrAű{* c4,2MSV$A7 s`1Uz l : -ddFmx=j.mMbj&-_H9dh5h}澵HUudt/Mқd;'d́dlzxGNˎ4ѤL?80| 7q]c$#QX[]dL8u6҂Wy\\ډ=7\ìc+~͑/id1] .wl"h,J;]:\b~*́dn>L[Lt4̀<7ȶ!sbϠ94C.7CH4j)ߊZFZ>s//و+RtW/vnJѼZ;GZ6 0fJy3%w>grAAp2H c8l0R5nZm-jW)L~SMt,),|mnu,C]ĹE>oV~N->flaq&>Yc5FpSfGZ{:F\KJ<0FGnj +|܂TRJ=q7]mByIS "!m!@# 4\{eѥQe>x3l"Ud FwJۜs9_T4O>*D+ E[ Xx]*S fPOVO{ Q95!Ʋm_5z3$cG\lنV6`YbWZ>DVK`N/"BeRC}^x|y^Dk)&q<˅烈P](gb96o93EQ+͑!sD~E&$p(fEi etϫ뇰TyS*R2O:N%q.yN#{B@:Hّ_5^r '.+/kitS!wkpNJtHf-3zd>ߘ68;HPGm8gtQJn2UHk̜ =k[GN Vӝ;uF R&$F3pxeG qW>eM>ߵk}KJyOdfnKp=?w%QQQ '3˶H?wAp1E<+ꦵ{1S.bQ.>Ifkj$C& 5 @E'\jz!;@囯fG?rG>["Nc&QzOx(0cfl 3+ ՘׹Wo;Tto1姛ƼͷmvƧp|>>SVTlD5cKe+P.9 |A ~ś;m~sI9SJ )_صX.C.3 M~FsנLe֞1xLY хk@Vi&30\ 3mk$eG(N8UZՌIU[cteߎsOu2;i\gv {97WNga2u*;Z}s+2N X Ǜ#XOօ"cH'6'0PS2\mBX#DJ\ħX*U9V/,Dq x`wL90VVq<щMRǫB+K uLtf,~o+s}L@wY29mF:CwƢ7Ѹيu(4f XQwC’3S.nJ7>ԻLKf;(48N10|*miNM^iخws͹mLǶEHvb{y( QQY a`n:g `iX*Bi|R ]MהZjD_G@6u uKࢆ6 "/Aʽt-(n۾_*HV~B-rPp&0R,DSK#%kkܱ?ĕ0Gr=9xR3̆\a7"fH{? ^+GΧ%|%(2 7k679>-r2 @QJ&*B" rcr!H;'1 AdiroBܱ,F?ukT j((B.n%v[f2aꌵk& +كW/t͉=eQ4g-_H%zǠ[أ[ Y"icRU[0E4Zxث P6Mm|4QgVn҃Ufur*WUNNmԫ~v< >@."n,|eZHBB\MCD,e ?pU?ɚqEĴQ+ӊFp LNL.|v/؄CD'\.sӔ| 䅆Ä1-:?}G⒋VY4+H4)_ٿ͸U'v`tνFF6'͓;OZʩosѣxPw&c=rG:22N`aRv ඓ%CER<.R8[ǹ팅$~!Kh=byV2l)%gD f}v/G^Uڼ(:4R.V,I&h8(fѿ`~d4KbxFIENDB`gridtext/tests/figs/deps.txt0000644000176200001440000000010314310622556015670 0ustar liggesusers- vdiffr-svg-engine: 1.0 - vdiffr: 0.3.3 - freetypeharfbuzz: 0.2.5 gridtext/tests/figs/grid-renderer/0000755000176200001440000000000014310622556016733 5ustar liggesusersgridtext/tests/figs/grid-renderer/rendering-raster-data.svg0000644000176200001440000004744314310622556023652 0ustar liggesusers gridtext/tests/figs/grid-renderer/text-in-different-stylings.svg0000644000176200001440000000171714310622556024670 0ustar liggesusers blue red bold roman gridtext/tests/figs/grid-renderer/mixing-text-and-boxes.svg0000644000176200001440000000326614310622556023616 0ustar liggesusers text 1, square box blue text 2, rounded box filled gridtext/tests/figs/richtext-grob/0000755000176200001440000000000014310622556016763 5ustar liggesusersgridtext/tests/figs/richtext-grob/various-text-boxes.svg0000644000176200001440000001567014310622556023305 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.svg0000644000176200001440000001347714310622556022554 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.svg0000644000176200001440000001415114310622556024626 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.svg0000644000176200001440000001347614310622556022422 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/0000755000176200001440000000000014310622556016626 5ustar liggesusersgridtext/tests/figs/textbox-grob/multiple-boxes-internal-alignment.svg0000644000176200001440000001421414310622556026110 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.svg0000644000176200001440000001576714310622556027542 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.svg0000644000176200001440000001745714310622556027742 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.svg0000644000176200001440000001701514310622556024724 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.svg0000644000176200001440000001732514310622556030671 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.svg0000644000176200001440000001743214310622556030505 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.R0000644000176200001440000000011214310622556015227 0ustar liggesuserslibrary(testthat) library(grid) library(gridtext) test_check("gridtext") gridtext/src/0000755000176200001440000000000014310626566012704 5ustar liggesusersgridtext/src/rect-box.h0000644000176200001440000002003114310622556014567 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.h0000644000176200001440000000147314310622556014615 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.h0000644000176200001440000000573714310622556014042 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.cpp0000644000176200001440000001161014310622556014327 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.h0000644000176200001440000004336114310622556015417 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.h0000644000176200001440000000446314310622556014004 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.h0000644000176200001440000001226314310622556014424 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.h0000644000176200001440000000524514310622556014373 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.h0000644000176200001440000000057714310622556016137 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.h0000644000176200001440000000245514310622556014532 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.h0000644000176200001440000000306114310622556014622 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.h0000644000176200001440000000427014310622556014007 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.cpp0000644000176200001440000001742714310622556016045 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.h0000644000176200001440000000766114310622556015613 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.h0000644000176200001440000000027114310622556014331 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.cpp0000644000176200001440000005363314310622556015706 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; #ifdef RCPP_USE_GLOBAL_ROSTREAM Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); #endif // 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.cpp0000644000176200001440000000227514310622556016142 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.h0000644000176200001440000001141014310622556015133 0ustar liggesusers#ifndef RASTER_BOX_H #define RASTER_BOX_H #include using namespace Rcpp; #include // for pair<> using namespace std; #include "layout.h" inline 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/0000755000176200001440000000000014310622556012311 5ustar liggesusersgridtext/R/read-image.R0000644000176200001440000000112614310622556014427 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)) { curl::curl_fetch_memory(path)$content } else { path } } is_url <- function(path) { grepl("https?://", path) } gridtext/R/parse-css.R0000644000176200001440000000415614310622556014342 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.R0000644000176200001440000000075214310622556015015 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.R0000644000176200001440000000075314310622556014347 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.R0000644000176200001440000000511714310622556015555 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), "fontface")] <- NULL gp_new["fontface"] <- 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) { font_old <- drawing_context$gp$font # combine bold and italic if needed if (!isTRUE(overwrite)) { if (isTRUE(fontface == "italic") && isTRUE(font_old == 2)) { # see ?grid::gpar for fontface codes fontface <- "bold.italic" } else if (isTRUE(fontface == "bold") && isTRUE(font_old == 3)) { fontface <- "bold.italic" } } set_context_gp(drawing_context, gpar(fontface = fontface)) } gridtext/R/RcppExports.R0000644000176200001440000001025314310622556014726 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.R0000644000176200001440000000064614310622556014274 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.R0000644000176200001440000000613314310622556015046 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 #' `fontsize` 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 font <- gp$font %||% grid::get.gpar("font")$font fontsize <- gp$fontsize %||% grid::get.gpar("fontsize")$fontsize devname <- names(grDevices::dev.cur()) fontkey <- paste0(devname, fontfamily, font, 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, font, fontsize, cache) # descent and space width depend only on font l2 <- font_info(fontkey, fontfamily, font, 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, font, fontsize, cache) { info <- font_info_cache[[fontkey]] if (is.null(info)) { descent_pt <- convertHeight(grobDescent(textGrob( label = "gjpqyQ", gp = gpar( fontsize = fontsize, fontfamily = fontfamily, font = font, cex = 1 ) )), "pt", valueOnly = TRUE) space_pt <- convertWidth(grobWidth(textGrob( label = " ", gp = gpar( fontsize = fontsize, fontfamily = fontfamily, font = font, 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, font, 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, font = font, cex = 1 ) )), "pt", valueOnly = TRUE) width_pt <- convertWidth(grobWidth(textGrob( label = label, gp = gpar( fontsize = fontsize, fontfamily = fontfamily, font = font, 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.R0000644000176200001440000002704414310622556015224 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.R0000644000176200001440000001177714310622556015063 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.R0000644000176200001440000000423314310622556014521 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.R0000644000176200001440000003374714310622556015076 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.md0000644000176200001440000000130714310626057013207 0ustar liggesusers# gridtext 0.1.5 - Transition to curl package and drop RCurl dependency - Fix fontface not being processed and words spaced properly in R 4.2.0 - Maintainer changes to Brenton Wiernik - Removed LazyData from package DESCRIPTION to fix CRAN NOTE # 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/MD50000644000176200001440000001050014311055377012416 0ustar liggesusersf3d0555788d67826ba869802fdd0f970 *DESCRIPTION 39276fab68fb6f733821e2f243383213 *LICENSE 0857ec650ec6eaebb76f061ff2d67a68 *NAMESPACE 86d99a4c02df8f471228fb6b33c930f4 *NEWS.md b35088f9df099335362baa60c94fabb9 *R/RcppExports.R 1505f684fb64bc1430b1639f3b973720 *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 11fe35d92b60aeb21a5e1a039e048833 *R/read-image.R 14f402b6578dc90c2ccc237fd5985d9a *R/recycle-gpar.R 78d868e02a776fa994f8ef9c1b0c35f2 *R/richtext-grob.R b1effa033bea2bafdbc7696a60e7d458 *R/text-details.R a5bd8b3d723da070417833237a931d6a *R/textbox-grob.R bbe289c4fff2d847fd93d09f61b8731a *README.md a424816ce3f3342cdd6254853ef2ff55 *inst/extdata/Rlogo.png af9ea4783f9da353be659b69350558f1 *man/figures/README-unnamed-chunk-4-1.png b4e9131dcbdf1c21cafa4ebbe137c258 *man/figures/README-unnamed-chunk-5-1.png 7e57d63476e09ecde6b2b730c9f08819 *man/figures/README-unnamed-chunk-6-1.png ed4b2603a3a06bdd360e54455c711aa6 *man/figures/README-unnamed-chunk-7-1.png b5a7c31a715ce207b896f25fc573b1da *man/figures/README-unnamed-chunk-8-1.png 5c51af6b80be93402d056d83ef476473 *man/gridtext.Rd 670d7725c7c9e0122bac4bbbf9c49c6e *man/richtext_grob.Rd 725c355183ac984c7c998f776f344a7e *man/textbox_grob.Rd 7eb4d6f0f775937d765e69a38edbd6bb *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 667b6ab8e3d2f747074b121f21f6bd1c *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 4338fd4ea5ae39e415ec2e29860d85b4 *tests/figs/test_image.png 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 d9a7d5e06f4322eac9e14fddf2d6c974 *tests/testthat/helper-vdiffr.R 03978976647e2bcc1e69e02bc8b591b3 *tests/testthat/test-get_file.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/0000755000176200001440000000000014310622556013065 5ustar liggesusersgridtext/inst/extdata/0000755000176200001440000000000014310622556014517 5ustar liggesusersgridtext/inst/extdata/Rlogo.png0000644000176200001440000004223114310622556016311 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`