)Ej^ hUdZddlZddlmZddlmZddlmZmZm Z m Z m Z m Z m Z ddlmZmZmZerddlmZiZeeeezfed <d d iZe d Ze ed <eezezeezZe ed<e dedefZdedefdZededefdZdUdedededefdZ eddd GddZ!d ed!<d"ed#<d$ed%<d&ed'<eddd Gd(d)Z"d*ed+<d&ed,<eddd Gd-d.Z#d ed/<d0ed1<d2ed3<d4ed5<d6ed7<d&ed8< dVd9ed:ed;edZ% dWd9eezezeeze#ze"ze!zd:ed;ede"e!ze#zfd@Z& dVdAedBedCedDedz&DeviceRGB.serialize..{*BBs c**BBBBBBr!joinrDrAr@s r serializezDeviceRGB.serializez5xxBBdkBBBBBEXEXEXXXr!ct|j|jz dkot|j|jz dkS)Ng& .>)absr1r2r3r@s r is_achromaticzDeviceRGB.is_achromatic}s746DF?##d*Js46DF?/C/Cd/JJr!rc\td|jzd|jzzd|jzzS)Ngz6?g,C?g]m{?)rr1r2r3r@s r to_grayzDeviceRGB.to_grays,&46/FTVO;ftvoMNNNr!rL)rr)__name__ __module__r__doc__r9__annotations__rr r;propertystrrAtuplerDrHrUboolrYr[r?r!r rrFs55" HHH HHH HHH# X X  X  X  X F  X  X X X X#X(eUE12(((X(:5u!45:::X:Y3YYYYKtKKKKOOOOOOr!rz8The red color component. Must be in the interval [0, 1].z DeviceRGB.rz:The green color component. Must be in the interval [0, 1].z DeviceRGB.gz9The blue color component. Must be in the interval [0, 1].z DeviceRGB.baD The alpha color component (i.e. opacity). Must be `None` or in the interval [0, 1]. An alpha value of 0 makes the color fully transparent, and a value of 1 makes it fully opaque. If `None`, the color will be interpreted as not specifying a particular transparency rather than specifying fully transparent or fully opaque. z DeviceRGB.aceZdZUdZeed<eeed< d dedeeddfdZe de fdZ e de eeeffdZ e de eeeffd Zde fd ZdS) rz,A class representing a PDF DeviceGray color.r2r4Nrc t|dtt|t|d|tt|nddS)Nr2r4r6)r:r2r4s r r;zDeviceGray.__init__s` 4eKNN&;&;<<<4q}eKNN&;&;&;RVWWWWWr!cdS)r=r2r?r@s r rAzDeviceGray.operator sr!c*|j|j|jfS)zTThe color components as a tuple in order (r, g, b) with alpha omitted, in range 0-1.r2r@s r rDzDeviceGray.colorssvtvtv%%r!c<d|jzd|jzd|jzfSrFrir@s r rHzDeviceGray.colors255s#TV|S46\3<77r!c>t|jd|jS)NrJ)rr2rAr@s r rUzDeviceGray.serializes"''99$-999r!rL)r\r]rr^r9r_rr r;r`rarArbrDrHrUr?r!r rrs$66 HHH #XX X F X  XXXX#X&eUE12&&&X&85u!45888X8:3::::::r!rz} The gray color component. Must be in the interval [0, 1]. A value of 0 represents black and a value of 1 represents white. z DeviceGray.gz DeviceGray.ac eZdZUdZeed<eed<eed<eed<eeed< d dededededeeddf d Ze de fd Z e de eeeeffd Z de fd ZdS)rz,A class representing a PDF DeviceCMYK color.cmykr4Nrc *t|dtt|t|dtt|t|dtt|t|dtt|t|d|tt|nddS)Nrmrnrorpr4r6)r:rmrnrorpr4s r r;zDeviceCMYK.__init__s 4eKNN&;&;<<<4eKNN&;&;<<<4eKNN&;&;<<<4eKNN&;&;<<<4q}eKNN&;&;&;RVWWWWWr!cdS)r=rpr?r@s r rAzDeviceCMYK.operatorrgr!c6|j|j|j|jfS)zWThe color components as a tuple in order (c, m, y, k) with alpha omitted, in range 0-1.)rmrnrorpr@s r rDzDeviceCMYK.colorssvtvtvtv--r!c`dd|jDd|jzS)NrJc34K|]}t|VdSrLrMrNs r rQz'DeviceCMYK.serialize..rRr!rSr@s r rUzDeviceCMYK.serializerVr!rL)r\r]rr^r9r_rr r;r`rarArbrDrUr?r!r rrs666 HHH HHH HHH HHH# X X  X  X  X  X F  X  X X X X#X.eUE589...X.Y3YYYYYYr!rzDeviceCMYK.OPERATORz9The cyan color component. Must be in the interval [0, 1].z DeviceCMYK.czsc( } !HHu   a5%((U*E!HHu,NPQ  r!hexstrct|tst|d|dst |dt |}|dkrOt |ddzd}t |ddzd}t |ddzd}d }n5|d kret |ddzd}t |ddzd}t |ddzd}t |ddzd}n|d krKt |ddd}t |dd d}t |d d d}d }ny|d krat |ddd}t |dd d}t |d d d}t |d d d}nt |d t||||S)a Parse an RGB color from a css-style 8-bit hexadecimal color string. Args: hexstr (str): of the form `#RGB`, `#RGBA`, `#RRGGBB`, or `#RRGGBBAA` (case insensitive). Must include the leading octothorp. Forms omitting the alpha field are interpreted as not specifying the opacity, so it will not be explicitly set. An alpha value of `00` is fully transparent and `FF` is fully opaque. Returns: DeviceRGB representation of the color.  is not of type strr}z does not start with #r N z0 could not be interpreted as a RGB(A) hex string)r~ra TypeErrorrr+lenintrx)rhlenr1r2r3r4s r rrZs  fc " "86666777   S ! !<F:::;;; v;;D qyy q A r " " q A r " " q A r " "   q A r " " q A r " " q A r " " q A r " "  qs R  qs R  qs R    qs R  qs R  qs R  qs R FTTTUUU 1a  r!rgbstrct|tst|d|dd}|dr|dst |d|dd}|d }t|d kr#d |D\}}}t|||d St|dkrtd|DSt |d)z Parse an RGB color from a css-style rgb(R, G, B, A) color string. Args: rgbstr (str): of the form `rgb(R, G, B)` or `rgb(R, G, B, A)`. Returns: DeviceRGB representation of the color. rrJzrgb()z- does not follow the expected rgb(...) formatrr{,rc34K|]}t|VdSrLrrOrms r rQz(color_from_rgb_string..s(**a3q66******r!N)r4c,g|]}t|Sr?rrs r z)color_from_rgb_string..s---c!ff---r!z6 could not be interpreted as a rgb(R, G, B[, A]) color) r~rarreplacerendswithr+splitrrx)rrDr1r2r3s r color_from_rgb_stringrs# fc " "86666777 ^^C $ $F   V $ $SFOOC,@,@SFQQQRRR AbD\F \\#  F 6{{a**6***1aAq!t$$$$ 6{{a--f---.. VVV W WWr!)r-r.ceZdZUdZeed< eed< defdZdddefdZdddefdZ defd Z e dd Z e dd Z e dd Ze deddfd Ze deddfdZe deddfdZe deddfdZdeefdZdefdZdedefdZe ddZdefdZdS)PointzM An x-y coordinate pair within the two-dimensional coordinate frame. xrorcXt|jdt|jS)z=Render the point to the string `"x y"` for emitting to a PDF.rJrrror@s r renderz Point.renders, ''AA-*?*?AAAr!otherct|tstd||j|jz|j|jzzS)a( Compute the dot product of two points. Args: other (Point): the point with which to compute the dot product. Returns: The scalar result of the dot product computation. Raises: TypeError: if `other` is not a `Point`. zcannot dot with )r~rrrror:rs r dotz Point.dotsJ%'' :8u8899 9v$&57"222r!c t|tstd||j|jz|j|jzz }|dk|dkz }||zdkrdS|t jt| |||zz dzS)a Compute the angle between two points (interpreted as vectors from the origin). The return value is in the interval (-pi, pi]. Sign is dependent on ordering, with clockwise angle travel considered to be positive due to the orientation of the coordinate frame basis vectors (i.e. the angle between `(1, 0)` and `(0, 1)` is `+pi/2`, the angle between `(1, 0)` and `(0, -1)` is `-pi/2`, and the angle between `(0, -1)` and `(1, 0)` is `+pi/2`). Args: other (Point): the point to compute the angle sweep toward. Returns: The scalar angle between the two points **in radians**. Raises: TypeError: if `other` is not a `Point`. zcannot compute angle with rr$) r~rrrromagmathacosroundr)r:r signifiersigns r anglez Point.angles(%'' DBBBCC CVeg%$&57*:; Q9q=1 88:: #q ( (3didhhuooeiikk9Q&RTU V VWWWWr!c@tj|j|jS)a Compute the Cartesian distance from this point to the origin This is the same as computing the magnitude of the vector represented by this point. Returns: The scalar result of the distance computation. )rhypotrror@s r rz Point.magsz$&$&)))r!ct|tr+t|j|jz|j|jzStS)an Produce the sum of two points. Adding two points is the same as translating the source point by interpreting the other point's x and y coordinates as distances. Args: other (Point): right-hand side of the infix addition operation Returns: A Point which is the sum of the two source points. rror~rrroNotImplementedrs r __add__z Point.__add__sB eU # # A46EG+tv/?@@@ @r!ct|tr+t|j|jz |j|jz StS)a0 Produce the difference between two points. Unlike addition, this is not a commutative operation! Args: other (Point): right-hand side of the infix subtraction operation Returns: A Point which is the difference of the two source points. rrrs r __sub__z Point.__sub__ sB eU # # A46EG+tv/?@@@ @r!c<t|j |j S)z Produce a point by negating this point's coordinates. Returns: A Point whose coordinates are this points coordinates negated. r)rrror@s r __neg__z Point.__neg__sw46'****r!ct|tr/t|}t|j|z|j|zSt S)a  Multiply a point by a scalar value. Args: other (Number): the scalar value by which to multiply the point's coordinates. Returns: A Point whose coordinates are the result of the multiplication. )r~rr9rrror)r:r float_others r __mul__z Point.__mul__)sF e[ ) ) E,,K+-tv /CDD Dr!c,||SrLrrs r __rmul__zPoint.__rmul__;s||E"""r!ct|tr:t|jt |z |jt |z St S)a Divide a point by a scalar value. .. note:: Because division is not commutative, `Point / scalar` is implemented, but `scalar / Point` is nonsensical and not implemented. Args: other (Number): the scalar value by which to divide the point's coordinates. Returns: A Point whose coordinates are the result of the division. r~rrrr9rorrs r __truediv__zPoint.__truediv__?sG e[ ) ) G%,,.u0EFF Fr!ct|tr:t|jt |z|jt |zSt S)a Divide a point by a scalar value using integer division. .. note:: Because division is not commutative, `Point // scalar` is implemented, but `scalar // Point` is nonsensical and not implemented. Args: other (Number): the scalar value by which to divide the point's coordinates. Returns: A Point whose coordinates are the result of the division. rrs r __floordiv__zPoint.__floordiv__TsG e[ ) ) I5<</5<<1GHH Hr!c#.K|jV|jVdS)z/Iterate over point coordinates in (x, y) order.Nrr@s r __iter__zPoint.__iter__is f f r!cdS)7Length to mimic tuple-like behaviour for compatibility.rr?r@s r __len__z Point.__len__nqr!idxc*|j|jf|S)z0Indexable access to coordinates in (x, y) order.rr:rs r __getitem__zPoint.__getitem__rs$$r! Transformct|tr[t|j|jz|j|jzz|jz|j|jz|j |jzz|j zStS)a Transform a point with the given transform matrix. .. note:: This operator is only implemented for Transforms. This transform is not commutative, so `Point @ Transform` is implemented, but `Transform @ Point` is not implemented (technically speaking, the current implementation is commutative because of the way points and transforms are represented, but if that representation were to change this operation could stop being commutative) Args: other (Transform): the transform to apply to the point Returns: A Point whose coordinates are the result of applying the transform. r) r~rrr4rrmroer3dfrrs r __matmul__zPoint.__matmul__xsw& eY ' ' 'DF"UWtv%55?'DF"UWtv%55?  r!c\dt|jdt|jdS)Nz(x=z, y=rrr@s r __str__z Point.__str__s/H]46**HH df0E0EHHHHr!N)rrrr)rr)rrrr)r\r]rr^r9r_rarrrrr#rrrr rrrrrrrrrrrr?r!r rrsu HHH$ HHH$BBBBB 33U3333$X7XuXXXX: *U * * * *^$^"+++^+V^"#f####^#G^(&W^((5/ %s%u%%%% ^4IIIIIIIr!rc@eZdZUdZeed<eed<eed<eed<eed<eed<deefd Zdefd Z d edefd Z e d1d Z e de de ddfdZe d2de dee ddfdZe de ddfdZe de ddfdZe d2de dee ddfdZe d3de dee ddfdZe d3de dee ddfdZde de ddfdZd2de dee ddfd Zde ddfd!Zde ddfd"Zd2de dee ddfd#Zd3de dee ddfd$Zd3de dee ddfd%Zde de ddfd&Zd1d'Zed(e ddfd)Z ed(e ddfd*Z!ed4d+Z"d,d-de#e$d-ffd.Z%de$fd/Z&de#eeffd0Z'dS)5rax A representation of an affine transformation matrix for 2D shapes. The actual matrix is: ``` [ a b 0 ] [x' y' 1] = [x y 1] [ c d 0 ] [ e f 1 ] ``` Complex transformation operations can be composed via a sequence of simple transformations by performing successive matrix multiplication of the simple transformations. For example, scaling a set of points around a specific center point can be represented by a translation-scale-translation sequence, where the first translation translates the center to the origin, the scale transform scales the points relative to the origin, and the second translation translates the points back to the specified center point. Transform multiplication is performed using python's dedicated matrix multiplication operator, `@` The semantics of this representation mean composed transformations are specified left-to-right in order of application (some other systems provide transposed representations, in which case the application order is right-to-left). For example, to rotate the square `(1,1) (1,3) (3,3) (3,1)` 45 degrees clockwise about its center point (which is `(2,2)`) , the translate-rotate-translate process described above may be applied: ```python rotate_centered = ( Transform.translation(-2, -2) @ Transform.rotation_d(45) @ Transform.translation(2, 2) ) ``` Instances of this class provide a chaining API, so the above transform could also be constructed as follows: ```python rotate_centered = Transform.translation(-2, -2).rotate_d(45).translate(2, 2) ``` Or, because the particular operation of performing some transformations about a specific point is pretty common, ```python rotate_centered = Transform.rotation_d(45).about(2, 2) ``` By convention, this class provides class method constructors following noun-ish naming (`translation`, `scaling`, `rotation`, `shearing`) and instance method manipulations following verb-ish naming (`translate`, `scale`, `rotate`, `shear`). r4r3rmrrrrc#vK|jV|jV|jV|jV|jV|jVdS)z;Iterate over matrix components in (a, b, c, d, e, f) order.Nr4r3rmrrrr@s r rzTransform.__iter__sLf f f f f f r!cdS)rr?r@s r rzTransform.__len__rr!rcZ|j|j|j|j|j|jf|S)zBIndexable access to matrix components in (a, b, c, d, e, f) order.rrs r rzTransform.__getitem__s&?DDr!c"|ddddddS)zu Create a transform representing the identity transform. The identity transform is a no-op. r rr?)clss r identityzTransform.identityss1aAq!$$$r!rroc V|ddddt|t|S)aF Create a transform that performs translation. Args: x (Number): distance to translate points along the x (horizontal) axis. y (Number): distance to translate points along the y (vertical) axis. Returns: A Transform representing the specified translation. r rr9rrros r translationzTransform.translations*s1aAuQxxq222r!Nc^||}|t|ddt|ddS)a Create a transform that performs scaling. Args: x (Number): scaling ratio in the x (horizontal) axis. A value of 1 results in no scale change in the x axis. y (Number): optional scaling ratio in the y (vertical) axis. A value of 1 results in no scale change in the y axis. If this value is omitted, it defaults to the value provided to the `x` argument. Returns: A Transform representing the specified scaling. Nrrrs r scalingzTransform.scaling s4 9As588Q588Q222r!thetac|tj|tj|tj| tj|ddS)a# Create a transform that performs rotation. Args: theta (Number): the angle **in radians** by which to rotate. Positive values represent clockwise rotations. Returns: A Transform representing the specified rotation. r)rcossin)rrs r rotationzTransform.rotationsJs HUOOTXe__tx.>QRTU   r!theta_dcP|tj|S)a4 Create a transform that performs rotation **in degrees**. Args: theta_d (Number): the angle **in degrees** by which to rotate. Positive values represent clockwise rotations. Returns: A Transform representing the specified rotation. )rrradians)rrs r rotation_dzTransform.rotation_d.s ||DL11222r!c^||}|dt|t|dddS)a Create a transform that performs shearing (not of sheep). Args: x (Number): The amount to shear along the x (horizontal) axis. y (Number): Optional amount to shear along the y (vertical) axis. If omitted, this defaults to the value provided to the `x` argument. Returns: A Transform representing the specified shearing. Nr rrrs r shearingzTransform.shearing=s4 9As1eAhha!Q222r!raxayc ||}|dtjt|tjt|dddS)a Create a skew (shear) transform using angles **in radians**. Args: ax (Number): skew angle along the X axis (radians). Positive ax produces x' = x + tan(ax) * y ay (Number): optional skew angle along the Y axis (radians). Positive ay produces y' = y + tan(ay) * x If omitted, defaults to the value of `ax`. Returns: A Transform representing the specified skew. Nr r)rtanr9)rrrs r skewingzTransform.skewingOsH :Bs1dhuRyy))48E"II+>+>1aHHHr!ax_day_dcp||}tjt|}tjt|}d}ttj||ks%ttj||krt d|||S)u Create a skew (shear) transform using angles **in degrees**. Args: ax_d (Number): skew angle along X in degrees. ay_d (Number): optional skew angle along Y in degrees. If omitted, defaults to ax_d. Returns: A Transform representing the specified skew. Raises: ValueError: if an angle is too close to 90° + k·180° (infinite shear). Ng-q=u:Skew angle produces infinite shear (near 90° + k·180°).)rrr9rXrr+r)rrrrrepss r skewing_dzTransform.skewing_dbs <D \%++ & & \%++ & & tx||  s " "c$(2,,&7&7#&=&=YZZ Z{{2r"""r!c>|t||zS)a Produce a transform by composing the current transform with a translation. .. note:: Transforms are immutable, so this returns a new transform rather than mutating self. Args: x (Number): distance to translate points along the x (horizontal) axis. y (Number): distance to translate points along the y (vertical) axis. Returns: A Transform representing the composed transform. rrr:rros r translatezTransform.translate{si++Aq1111r!c>|t||zS)a Produce a transform by composing the current transform with a scaling. .. note:: Transforms are immutable, so this returns a new transform rather than mutating self. Args: x (Number): scaling ratio in the x (horizontal) axis. A value of 1 results in no scale change in the x axis. y (Number): optional scaling ratio in the y (vertical) axis. A value of 1 results in no scale change in the y axis. If this value is omitted, it defaults to the value provided to the `x` argument. Returns: A Transform representing the composed transform. )rrrs r scalezTransform.scales$i''1----r!c<|t|zS)a Produce a transform by composing the current transform with a rotation. .. note:: Transforms are immutable, so this returns a new transform rather than mutating self. Args: theta (Number): the angle **in radians** by which to rotate. Positive values represent clockwise rotations. Returns: A Transform representing the composed transform. )rr)r:rs r rotatezTransform.rotatesi((////r!c<|t|zS)a Produce a transform by composing the current transform with a rotation **in degrees**. .. note:: Transforms are immutable, so this returns a new transform rather than mutating self. Args: theta_d (Number): the angle **in degrees** by which to rotate. Positive values represent clockwise rotations. Returns: A Transform representing the composed transform. )rr)r:rs r rotate_dzTransform.rotate_ds i**73333r!c>|t||zS)a% Produce a transform by composing the current transform with a shearing. .. note:: Transforms are immutable, so this returns a new transform rather than mutating self. Args: x (Number): The amount to shear along the x (horizontal) axis. y (Number): Optional amount to shear along the y (vertical) axis. If omitted, this defaults to the value provided to the `x` argument. Returns: A Transform representing the composed transform. )rrrs r shearzTransform.shears i((A....r!c>|t||zS)zCompose with a skew (radians).)rr)r:rrs r skewzTransform.skewsi''B////r!c>|t||zS)zCompose with a skew (degrees).)rr)r:rrs r skew_dzTransform.skew_dsi))$5555r!czt| | |zt||zS)a Bracket the given transform in a pair of translations to make it appear about a point that isn't the origin. This is a useful shorthand for performing a transform like a rotation around the center point of an object that isn't centered at the origin. .. note:: Transforms are immutable, so this returns a new transform rather than mutating self. Args: x (Number): the point along the x (horizontal) axis about which to transform. y (Number): the point along the y (vertical) axis about which to transform. Returns: A Transform representing the composed transform. rrs r aboutzTransform.abouts8&$$aR!,,t3i6K6KAq6Q6QQQr!c X|j|jz|j|jzz }|dkrt dt |j|z |j |z |j |z |j|z |j|jz|j|jzz |z |j|jz|j|jzz |z S)z Produce a transform that is the inverse of this transform. Returns: A Transform representing the inverse of this transform. Raises: ValueError: if the transform is not invertible. rzTransform is not invertibler)r4rr3rmr+rrr)r:dets r inversezTransform.inversesftvo/ !88:;; ;fslvgmvgmfslv$&0C7v$&0C7     r!rc t|trTt|}t|j|z|j|z|j|z|j|z|j|z|j |zStS)z Multiply the individual transform parameters by a scalar value. Args: other (Number): the scalar value by which to multiply the parameters Returns: A Transform with the modified parameters. r) r~rr9rr4r3rmrrrrrs r rzTransform.__mul__ sv e[ ) ) %LLE&5.&5.&5.&5.&5.&5.  r!c,||SrLrrs r rzTransform.__rmul__!s||E"""r!c t|tr||j|jz|j|jzz|j|jz|j|jzz|j|jz|j|jzz|j|jz|j|jzz|j|jz|j|jzz|jz|j|jz|j|jzz|jzStS)z Compose two transforms into a single transform. Args: other (Transform): the right-hand side transform of the infix operator. Returns: A Transform representing the composed transform. r) r~r __class__r4r3rmrrrrrs r rzTransform.__matmul__&s eY ' ' >>&57"TVeg%55&57"TVeg%55&57"TVeg%55&57"TVeg%55&57"TVeg%55?&57"TVeg%55? " r! last_itemrc t|jdt|jdt|jdt|jdt|jdt|jd |fS)z Render the transform to its PDF output representation. Args: last_item: the last path element this transform applies to Returns: A tuple of `(str, last_item)`. `last_item` is returned unchanged. rJz cmrr4r3rmrrr)r:rs r rzTransform.render=sTV$$ B B}TV'<'< B BTV$$ B B'4TV'<'< B BTV$$ B B'4TV'<'< B B B    r!cdt|jdt|jdt|jdt|jdt|jdt|jd S)Nz transform: [rJz 0; z 1]rr@s r rzTransform.__str__Ns BTV$$ B B'4TV'<'< B BTV$$ B B'4TV'<'< B BTV$$ B B(5TV'<'< B B B r!c~tj|j|jtj|j|jfS)uM Returns (sqrt(a² + c²), sqrt(b² + d²)), i.e. the Euclidean norms of those rows. These values bound how much the transform can stretch geometry along the device X and Y axes, respectively, and are useful for inflating axis-aligned bounding boxes to account for stroke width under the CTM. )rrr4rmr3rr@s r row_normszTransform.row_normsVs/ 4646**DJtvtv,F,FGGr!)rrrL)rN)rrrr)(r\r]rr^r9r_rrrrr classmethodrr rrrrrrrrrrrrr r rrrr#rrrrbrarrrr?r!r rrs77r HHH HHH HHH HHH HHH HHH(5/EsEuEEEE%%%[% 3F 3v 3+ 3 3 3[ 33338F#33{333[3& V    [  3 3K 3 3 3[ 3333HV$43 333[3"III&)9I[III[I$##V#x/?#;###[#0262f22222"..v.(6"2.k....(0F0{0000"44;4444$//v/(6"2/k////$00v0x'70;00006666Xf-=66666RvR&R[RRRR*    .V ^.#f####^#^,  sL7H1I    "     H5.HHHHHHr!rz Transform.az Transform.bz Transform.cz Transform.dz Transform.ez Transform.f)r$r%rL)r{r{)-r^rcollections.abcr dataclassesrtypingrrrrr r r utilr rrdrawingrrdictrarcr_rrr7rrr#r9r,rrrrxrzrrrrrr ColorClassr?r!r r&s$ $$$$$$!!!!!!5444444444$######"$$sD3J $$$  &@AyAAA,x/?? I??? GCxV ,---1 q vef $d///;O;O;O;O;O;O;O0/;O|UVU $d///:::::::0/:D  $d///#Y#Y#Y#Y#Y#Y#Y0/#YL#( VB YW<@NN NN#N(0(8N)NNNN8++V+(+J++++000 ~hv..;jH9T0 00)j( 0000(GK #(.3;F3C82#2)j*@2222jX#X)j*@XXXX> $d###eIeIeIeIeIeIeI$#eIP $d###EHEHEHEHEHEHEH$#EHP Z 0 r!