From daad262fbce4dde7519d1c2a9f46e4751a0668cf Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Wed, 19 May 2010 11:55:17 +0000 Subject: [PATCH] Apply (with slight tweaks) patch from bug #48432 - Support for XSSF themes git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@946136 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 3 +- .../apache/poi/xssf/model/StylesTable.java | 13 +++- .../apache/poi/xssf/model/ThemesTable.java | 61 ++++++++++++++++++ .../poi/xssf/usermodel/XSSFCellStyle.java | 27 ++++++-- .../apache/poi/xssf/usermodel/XSSFColor.java | 30 ++++++++- .../poi/xssf/usermodel/XSSFRelation.java | 3 +- .../poi/xssf/usermodel/XSSFWorkbook.java | 12 ++++ .../poi/xssf/usermodel/TestXSSFCellStyle.java | 2 +- .../poi/xssf/usermodel/TestXSSFSheet.java | 2 +- .../extensions/TestXSSFCellFill.java | 19 ++++++ .../usermodel/helpers/TestColumnHelper.java | 2 +- test-data/spreadsheet/styles.xlsx | Bin 6557 -> 9309 bytes 12 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 src/ooxml/java/org/apache/poi/xssf/model/ThemesTable.java diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index b80709fb2f..9d34da5b1e 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,7 +34,8 @@ - 49244 -Support for data validation for OOXML format + 48432 - Support for XSSF themes + 49244 - Support for data validation for OOXML format 49066 - Worksheet/cell formatting, with view and HTML converter 49020 - Workaround Excel outputting invalid XML in button definitions by not closing BR tags 49050 - Improve performance of AbstractEscherHolderRecord when there are lots of Continue Records diff --git a/src/ooxml/java/org/apache/poi/xssf/model/StylesTable.java b/src/ooxml/java/org/apache/poi/xssf/model/StylesTable.java index 47d75efe5e..c0ad5f1bd1 100644 --- a/src/ooxml/java/org/apache/poi/xssf/model/StylesTable.java +++ b/src/ooxml/java/org/apache/poi/xssf/model/StylesTable.java @@ -74,6 +74,7 @@ public class StylesTable extends POIXMLDocumentPart { public static final int FIRST_CUSTOM_STYLE_ID = BuiltinFormats.FIRST_USER_DEFINED_FORMAT_INDEX + 1; private StyleSheetDocument doc; + private ThemesTable theme; /** * Create a new, empty StylesTable @@ -91,6 +92,14 @@ public class StylesTable extends POIXMLDocumentPart { readFrom(part.getInputStream()); } + public ThemesTable getTheme() { + return theme; + } + + public void setTheme(ThemesTable theme) { + this.theme = theme; + } + /** * Read this shared styles table from an XML file. * @@ -184,7 +193,7 @@ public class StylesTable extends POIXMLDocumentPart { styleXfId = (int) xfs.get(idx).getXfId(); } - return new XSSFCellStyle(idx, styleXfId, this); + return new XSSFCellStyle(idx, styleXfId, this, theme); } public int putStyle(XSSFCellStyle style) { CTXf mainXF = style.getCoreXf(); @@ -455,7 +464,7 @@ public class StylesTable extends POIXMLDocumentPart { xf.setXfId(0); int xfSize = styleXfs.size(); int indexXf = putCellXf(xf); - return new XSSFCellStyle(indexXf - 1, xfSize - 1, this); + return new XSSFCellStyle(indexXf - 1, xfSize - 1, this, theme); } /** diff --git a/src/ooxml/java/org/apache/poi/xssf/model/ThemesTable.java b/src/ooxml/java/org/apache/poi/xssf/model/ThemesTable.java new file mode 100644 index 0000000000..d78e31073f --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/model/ThemesTable.java @@ -0,0 +1,61 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.xssf.model; + +import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTColorScheme; +import org.openxmlformats.schemas.drawingml.x2006.main.ThemeDocument; +import org.openxmlformats.schemas.drawingml.x2006.main.CTColor; + +/** + * Class that represents theme of XLSX document. The theme includes specific + * colors and fonts. + * + * @author Petr Udalau(Petr.Udalau at exigenservices.com) - theme colors + */ +public class ThemesTable extends POIXMLDocumentPart { + private ThemeDocument theme; + + public ThemesTable(PackagePart part, PackageRelationship rel) throws Exception { + super(part, rel); + theme = ThemeDocument.Factory.parse(part.getInputStream()); + } + + public ThemesTable(ThemeDocument theme) { + this.theme = theme; + } + + public XSSFColor getThemeColor(int idx) { + CTColorScheme colorScheme = theme.getTheme().getThemeElements().getClrScheme(); + CTColor ctColor = null; + int cnt = 0; + for (XmlObject obj : colorScheme.selectPath("./*")) { + if (obj instanceof org.openxmlformats.schemas.drawingml.x2006.main.CTColor) { + if (cnt == idx) { + ctColor = (org.openxmlformats.schemas.drawingml.x2006.main.CTColor) obj; + return new XSSFColor(ctColor.getSrgbClr().getVal()); + } + cnt++; + } + } + return null; + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java index 00dc1c4a5d..0bbbd5ff07 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java @@ -25,6 +25,7 @@ import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.xssf.model.StylesTable; +import org.apache.poi.xssf.model.ThemesTable; import org.apache.poi.xssf.usermodel.extensions.XSSFCellAlignment; import org.apache.poi.xssf.usermodel.extensions.XSSFCellBorder; import org.apache.poi.xssf.usermodel.extensions.XSSFCellFill; @@ -58,6 +59,7 @@ public class XSSFCellStyle implements CellStyle { private CTXf _cellStyleXf; private XSSFFont _font; private XSSFCellAlignment _cellAlignment; + private ThemesTable _theme; /** * Creates a Cell Style from the supplied parts @@ -65,11 +67,12 @@ public class XSSFCellStyle implements CellStyle { * @param cellStyleXfId Optional, style xf * @param stylesSource Styles Source to work off */ - public XSSFCellStyle(int cellXfId, int cellStyleXfId, StylesTable stylesSource) { + public XSSFCellStyle(int cellXfId, int cellStyleXfId, StylesTable stylesSource, ThemesTable theme) { _cellXfId = cellXfId; _stylesSource = stylesSource; _cellXf = stylesSource.getCellXfAt(this._cellXfId); _cellStyleXf = stylesSource.getCellStyleXfAt(cellStyleXfId); + _theme = theme; } /** @@ -394,7 +397,11 @@ public class XSSFCellStyle implements CellStyle { int fillIndex = (int)_cellXf.getFillId(); XSSFCellFill fg = _stylesSource.getFillAt(fillIndex); - return fg.getFillBackgroundColor(); + XSSFColor fillBackgroundColor = fg.getFillBackgroundColor(); + if (fillBackgroundColor != null && fillBackgroundColor.getCTColor().isSetTheme() && _theme != null) { + extractColorFromTheme(fillBackgroundColor); + } + return fillBackgroundColor; } /** @@ -422,7 +429,11 @@ public class XSSFCellStyle implements CellStyle { int fillIndex = (int)_cellXf.getFillId(); XSSFCellFill fg = _stylesSource.getFillAt(fillIndex); - return fg.getFillForegroundColor(); + XSSFColor fillForegroundColor = fg.getFillForegroundColor(); + if (fillForegroundColor != null && fillForegroundColor.getCTColor().isSetTheme() && _theme != null) { + extractColorFromTheme(fillForegroundColor); + } + return fillForegroundColor; } /** @@ -1385,7 +1396,15 @@ public class XSSFCellStyle implements CellStyle { int xfSize = _stylesSource._getStyleXfsSize(); int indexXf = _stylesSource.putCellXf(xf); - return new XSSFCellStyle(indexXf-1, xfSize-1, _stylesSource); + return new XSSFCellStyle(indexXf-1, xfSize-1, _stylesSource, _theme); } + /** + * Extracts RGB form theme color. + * @param originalColor Color that refers to theme. + */ + private void extractColorFromTheme(XSSFColor originalColor){ + XSSFColor themeColor = _theme.getThemeColor(originalColor.getTheme()); + originalColor.setRgb(themeColor.getRgb()); + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFColor.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFColor.java index 6c63195d40..6ed2a40524 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFColor.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFColor.java @@ -45,6 +45,11 @@ public class XSSFColor { ctColor.setRgb(new byte[]{(byte)clr.getRed(), (byte)clr.getGreen(), (byte)clr.getBlue()}); } + public XSSFColor(byte[] rgb) { + this(); + ctColor.setRgb(rgb); + } + /** * A boolean value indicating the ctColor is automatic and system ctColor dependent. */ @@ -80,6 +85,27 @@ public class XSSFColor { return ctColor.getRgb(); } + /** + * Standard Alpha Red Green Blue ctColor value (ARGB) with applied tint. + */ + public byte[] getRgbWithTint() { + byte[] rgb =ctColor.getRgb(); + for(int i = 0; i < rgb.length; i++){ + rgb[i] = applyTint(rgb[i] & 0xFF, ctColor.getTint()); + } + return rgb; + } + + private static byte applyTint(int lum, double tint){ + if(tint > 0){ + return (byte)(lum * (1.0-tint) + (255 - 255 * (1.0-tint))); + } else if (tint < 0){ + return (byte)(lum*(1+tint)); + } else { + return (byte)lum; + } + } + /** * Standard Alpha Red Green Blue ctColor value (ARGB). */ @@ -91,8 +117,8 @@ public class XSSFColor { * Index into the collection, referencing a particular or * value expressed in the Theme part. */ - public int getTheme() { - return (int)ctColor.getTheme(); + public int getTheme() { + return (int)ctColor.getTheme(); } /** diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java index d2ba007e3a..f11ebd655c 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java @@ -40,6 +40,7 @@ import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.SingleXmlCells; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.model.Table; +import org.apache.poi.xssf.model.ThemesTable; /** * @@ -232,7 +233,7 @@ public final class XSSFRelation extends POIXMLRelation { "application/vnd.openxmlformats-officedocument.theme+xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme", "/xl/theme/theme#.xml", - null + ThemesTable.class ); public static final XSSFRelation CALC_CHAIN = new XSSFRelation( "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml", diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 20aeadb50f..2fef9ad5f4 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -56,6 +56,7 @@ import org.apache.poi.xssf.model.CalculationChain; import org.apache.poi.xssf.model.MapInfo; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.StylesTable; +import org.apache.poi.xssf.model.ThemesTable; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlOptions; @@ -113,6 +114,8 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable+Nu&hDyrS9k68yj^=eYcJcX3J8e!0Av6v005u{xL`eDb%p}~a*zOk zhX5#?zNC|*yM?2>vF0mh3pYa!ZwGs-ylFV58~{9Q{XgS>*A5INDJl1G;e>pbou~fx zh~6D94S*ujrXwq(sFOxPe0up0j|FG z38@ur%Ijoho7SP(-9bs8&v?<@EGFO(@IGMH&(ohDIQyzIxRiaPJbG-&Ii@C9{^f?S z&Xq=!YJ_aI$v}G?Yc-+>@tevfcYe?tn{vJx`zt9p(2F=Wsqol58p;kn;RKSp5f>RB zwq^$myF)PA;hPj1b=QvSoiMwdNC^d1kWkUQ30^fJ{5CDMtdjx>$pKPyQi;MOQ zMkqy?X<-0{;f!4^?AEllFoJf^DU`$=NROtcpk?Tx()k}^%FuZwGOl`8(acoLucpot*4`c$$AvAMpqEVQc?y zkG8}S+uukN)E+bjbV+cuNP5~HEncPn9l)TrI@-&ha?a=^nkd;KHo%cf7({mi6df(P zT`4;3B}BG;s})Djj9OP%rUzxZ5NsC_4x*ZB7=K$^{89`g=@QTw(4*{jBLz95%ZsE( z3Y|SHC>VcP#q2`i0RF;pnqfvvvhDF|>cJ;rrAF?X$=9Y3=9pxVb2&eia!~B>ya6$P@K5o69 z6beg|XeqayG6KEQBM1%S2=zzDd~=p?yOwcIrh>+KV8-0)`9P-986lyB5P>Y>{;+Xbldoy9K?0JL2+PcB6D>N^Kiin)lpiV7N)AI zjduQ4>G_z9u_<|)rpO05Kw~LdX$k+0|^@hZnvgb4|QNEJJBF$|&h=2OQ zIu_xR6YiNw9qv4z;K-^l<#3j`J*oC6lrEy($DNuIzSmBIp2hzAhNRL_p`YO@{7hzg zXCK?ERRaI&2@L*oE6*!NU%#%tyCDh`FL5$Z_md+YJtSMfJVT)8W_i$Y(f{2+v zFCBJL7*Ps3!<^CNP1~obN%TcU!pHTd4km_6H{%}Lpkw#XNBCV#R(iYB`r4kZSIC`T zxGSf3J$&oIS#U3xP?Rdzq%+R$WGWKJ*k13gaqm0Ut5vjKz~7$Hr|Z8}E*g`!sFr&0 zQDiFhh;-5t==-^gH12~y%F)@dXCu`82-*DjGrixve3`D-F)}_kiAw6|T<)#E{If7c zK`48O6TmM&qnnT^JXE-aHwrCwvQ#JP2*_~-7!?a8coUz&Xbu8~uX4o5fJ{FFobU|N zswF!NNXFaZPLY%}q1Rkpakv;hRapV|JC_=#JC})TVIwh=ckVY^W5+#|?Ce%695F$w z-Kk6d=bO`%`J!3=C*O_5l@p z@VWFwoQzbS{yHl85vn^+s0%$KtxE?%4~~Wv5>}um+IAK?`Etb*ON%Bfmp5M8V#iDf zM%fEq=H$Vq5NoteQj>MScR?$14*3Vpk|!)W-hxpGZWmnf2GV>%wdC zV958MFT@F&9`KN{lEtMl6>MH4#o_HAsO`*n#9o0EMAWt@q{$T(l(-9M8RH&BdDAaXR(jgkdJ%dEvQGGfk+sOmq7KOiQ{ru;9Y4)tCQFVdTMZ|8;Qz4fo#MwXg9~W` z6_z7+F{0<~kK=3XKRdu(Jp%h9NVc*&;?vS>jE$puYaM45G2IwETxASo|TTaufX(0`Sy+Vjn{`X zwczag`#9r1Sqg9IJ zv5*A|22!+c*w6$j%Aw%V8+6YOW2TNzd2dZ5KO^QpP4AjQh?u0Hgsylz1Cu{+<+dC| z8@l8%tE)r+q71TAKF&aX(W!U*iNiffD z%9+0_I=X&g9~s>I!Fz?qkWNyVp(M6$aZsd`n9-uzlmmgvQ{Jy&M!UeV`lBuk&h55m@iA;142)4@YEV1ADH4irF5~A6&gCKfOmI~S3l}Dj zIv3)qf*)LKB+i;&c4t;7J{CZ;b=EN1?I>=!I!&w$0%WE{q_#RI`} zWK@(M<#bMlR5(E9SHTR1$MA#8dxDm7;S;W98oYz%7y_@rgD)4+R|6djYe>HiS4GNQ zVyG7q_1*Jmo%+L1Bkh{)!%;fABFQ{8hEfo+tgD73#9aa5-UOA9H2_!NLckpW%a>wS|Sd z8|QBm_Yc1nt*+z*<-%;o-U`6+HomE*dlMx1VKh$%8zJ<}%xV(ikek3HJ2ZYU9oqoS z6I06}3|!5ppuDUT@^yc4`ymbUk|a_Z3mqs~aSVLWD3EF_kb`HpWUD}7=iQBHb)CrB zO8X&LO* zI-QQpS^PxsR5($pE<-wiL{awLQoYPO7b=44vK8Oz_7+wu=pkgv3psXMsk~3_Ac8_> zxwL|sQlZwXK6QfbeV%Rg1j^v1`F&an%B)_sPD?-}o
V;m|9iJeG9Q<-xL3Sq0zs1 zQNZR&Lu(8MMb;KDTcVqtlNS4$s(tq(?xn@&L}X5xd}^GkrS-{r(h{wdb~UzhvvT@l zSPlq_sOqz5yg^`|b#KK$0SUZx%-m7I%^B%FEJ^d2poIR(aX=Rq)DhWpczD^@=4K%N@ zWRM2>6tPDh_~0gV-dk;4OF|6a7ZziwGMVq4x{g^RZMji8FKrj~?@zvY(Tn9v$Rkm- z6nHvkWK<_rUO-qffpFq&hj|H?kS|RE_jQ9lbzR4NmXrjeDGM2(JXW5uF-J$J%9_nu zv(8apN@T=34U7xSxKvz4Ih@VY>qvjSP0Lqo zwQ!dQ#+>}zQrBpINBH*vO^kdnQ>eoP03Q5n&)i?xTl}_JL)s2@P%fN1k(mG-ThYqu zlR2MSL;2@3FG=(FUyhyykqyR(r9@XG6}**;594*^Id!5Oh6 zzxM*WRu>R6qi`3&FVfX~3w6o&I}DEI3Y0W1uXzQLuZv^N?TnQP z-pd?$jB52Tnr*%?x+@jBww4&NyFlJ9^wbrl@_OYwvd^pjbXtzRUwKPt5-T@bc5K!f zUdTpnD;0Yy%X!AK_>3@N5T3vWfBABrFF=n?R;FT(eua%Ct*ZfW&I4cZESCda=Helh zCsWR3?rSX9@_T}U&wJdugFkkw9`4_ikclp#x^A!piSHp^^KW^W@Z@$^|KVU zMfCGdD`_3UmgZmqVXmnKG|=csgnAv0o445Hm1QCV8fp$bR7mC!X}^vtbH%!)Rh;=r zR6XH#p$Q_=I&y2wV|7KIWFaEoEU%|;vy{RXt>Nz(xYY`Wr4*;}KVmdtfcD&D;8dn` zPqae)>7t0jn+5}4;9r3m(1$QmIL;aqp7_bAGpBdaSkl3AYn`X{()EBDq^~!6iDx4P zU0GcbQ*vSYf#)7Ke4lsdgKPBkgs%Y?K$%6m=+IUtEtmIa&e*NP&9A4gu^wHCZIpa0 zR<&lvI0xj8=D&o4*GucINWANLPeIJw@oO-|agJO1>2XCuSpPBkbe1AFn(D;kADJN5a7SBY$ z7AQ9Yl5nNx*-(WtLU(ogTE}^1p7TlTB~ty&*BYyb&%#!V<)Nq4?nYtH?_Z8Vl;SBI z;O=A})~uonTr4k`y;X5W3%&NJFk_I6#p2D}*X609U;! zOd;QtX_09t6RX?nFw=Pc2CtXesL`$WD47QL25asw_$`Y~N>+-^$UPE+o?j@QWkGQIqtdM&YeNM)| ztm|6SYGtZ595LfMRHDxw@La83_k^Y4nZ43K(yOP*uj(Gn+`snPR@-LOKr=xTtYgSe zu3zXoQ8bDO%?#O*!iZPj4%Kg(^c`{6JIY)7^2KQRI)2)DR^5GO>;72Pg4xZb>YF63 zUKE4gmwAahNW$A(Xk8qR8xk)2r|89+&*Wxbppa)*MflI%T$I=X<|FjhU*I1hGr1%` zV>w$7&R&T>Z18b97dcG^P8Q(Mi+(xJFAW&zezDy3$mE?~Kby{&+iB)ge>_)XUDPm) zoOKj4EgoUFXoJZ(qVj_H11=_;3k*nb-*HG)brm1D|7y?OGCO*dMo<^A40XrH4kTyb zc*YgcG3V=e$v?RxyKGHGI^p6qYLCzWq*h&AvJksd2JWukGBC}&9XOd%X}9u@y_9na zi)@O0+!tDm(iw5!F1Qr9k<AYdkkD$b&C&Ayd&veD!llB3(L!JB`vUa{p|4evG)8 z5e?KsN33#Jsv#B@ppdm!CmGd^h3N6Xn1D1BKf?^#FEex8e$jnq)DZe5FIedgUIg&Q zu>E~Qshi4*SAgcDx$A8RsG2c{RU$Ym5%XbA7JeTnyz8Zs21Dy1gePWOKs?i!Re#_PhIt~ZS2xUhPwxs#cytCO=E zrW^ZzKUSjuslGzNGdW z4lk#p6ZfL}B9Try1ZjN(^6)nhsPu9|;w2c4gU)smR{O|DM5q z(J0u0{UWBXayQSByZ7i4&EL4A4J$uX{d?=`>qvi5zFNm^?dEtYir5df`>7v2iSO9Y z91(nHjdNv@`*kcq*j>y5on+yi&vTc$Mm|B;x0?-ex{I2z*K}ne7T|Fk#es$Ap(B+V z0gvG)nW6W+VR3XO2}yK?bPzcoX<-DPg6tRCLID%-?)Mvo6n&SM+L4@p%f}2fm#1%G zeAI>oH8_9dqlvTgKXm*-#NP)vamfivh10*pdKU1`+sS#{CWA(ikG}MZt0aE4VFWYV zOcV2wL4{?&EtbCA$A+R~chL^AY#}jWzw-tnYN<8D4=GUz1j}_!(L$Pgh5l{VeUagx z_&)id5C}<_V}CoB*#l8hS01li>@lMW-z8n`@Ly?^A;rtEiIFM@^0CcUpVJ9kQ@4KgM>1`?Hzp_lkx zwgKDPnQQ8gV;F*P5Jx;fBr`#GsK~K3_`+Tvj94*9pXxV!lEJyb!%@i@H}Lut%=96A z7nk=+Rq7;)q1E(*3IR2)w!LN-QgCTN3=^ zTk+GLrtcTrU;N8(lpnswiUbzH^Sa9w-Oy8ISV6Xf`KloR{ zKeyBVC}<5U%m4SywO?_5ZL0i4Duvw-`K873tMIQ~fSGU`Loaei2(r2z=GhvL;$~v|M`giqqsH2AH@H4V5=%1!I%aB PVE*{P$SOqr*M+y&i%UX_v;?-`+jMvfHBBGAP_caSX*1g9@ukFGlg2!)11>(uMCf5uDXLq2y0 zWAdf8u5SCnrysJ~fYdE>{6IB}!O;$t7b+M`gg4 zH-x@CaDDttIWhU?@Wxq=_(29LRpk8boO!w|58W%yJQ9;Q_@eJaLl}|L+Y;Sjkr%Ji zy&mX-JLYR?GmOlM>r`l~+3B;N363*Vd(7GQOrE03z*v^81fN+LYjy?wo}*@^w&t7S zaV|W6gc-Z;mg%hbY0n{EhaOq~ zZSSKm$H7hpP5BO|LX~$J@i|{>Jzq3UAPI1QBmjY?fh0J3`Rf-ZkOa4!Eo@zQj^geD z#C)jo&Cj?W8ve+h5x@{f?^4|?V0alF1j7Ak$bD#jWNiajmqCxSCNElA9n6O(|BA$8 z>Pyj8>{P@Ate;NiCIeG4+=|&Lwv6}LBZ%TGjvS}o3vJh0{?_e{!np0BQj{A(jOUA+ z5{mfk2#9=d!3J{3@v*%Vc&zhlGBcx)7G}zW?2AY%D!D!`QzRIoL!(Z%qS7o%mDN0M z#_uL5Ig@SoitoU@hEHcLDWu}AiY6h?eQpz%ArBStlF%z!>P0(ondUKGB={Ra-Orj8 z7m!(+n(hXcuH_w5fu;<6U18Gga0<4e51h$urIBV{*I%_mXLEZ8eDgysQ$2ihafLnK1gSyAGFD4M!HJWPiqtW_${K{ujd(6GqR6w0i1x-S@dY-J9ovhP zAM?BPD3wPrrtX*6%gGxk565bnfWYMT>U*7XK8(mQ$09LTt8ZXc{G`i2W=6bFB?i-dXXykXp zKS=H(k$5{CL4hJ_JWNGQQ7aC ziRQvi+lVJQXcfk=*IbitW&O706c$}Lu2feay>6u)2BVM^V^rK zaWC|_VY9E+okc-pJV#^a>6H*qUo8Yj&T%BDFN#rhg+~y#w2CTKeUrMnzPqp0rKkLY z(y>bTO`X0nA9|-+|4M#cdynzW8U3W#NCy2dm5QZ2MW@Iua*FfAcVr|epC@QMg3v(? z-jm_-xzZsY^4!2;%Giu9Lt^HW25p)5R%Cq+>|Mw!wUM`WN(*^Bu~*uxTN-LJ~BGH<*MYqaOVx?r_s%!#TeCryIqk)b1@{?3(q}pxTTi(TFShGG7_R$ zj3M~mF`ujy8o5-IwyK_q?V=wy^uuw$LraMNTmHRo$8T#^O-|I%45V6z0-FtV`!`$9;Ul zu(e+?FBJ7Au42^ourps-rZpu9nTJOvYGVrYVbs*Fw3k%&)rwp*oeAM?FOQe$?zFAA z`bsw~^y5t0oROT=wta!X{=2~pYGWB&R%+Zz63PIHK$F zX6s-{1^Ny6ejrn`?z1zRSfgPWPyBJ;UF1zuiO98;B`2uj|6(LL-d$P@Jx_buX`m)Nc;+re@TV1&!qDpFcasA)mi>ZL(1&|xmm~%GOA4T8 z*$_dW+1Pqe0vz)qnNbK$b_=2aJk|8b>TbNt&h*^}0MnCy}ub zjnsPVRD9-9Z;WPiX%q()FSq?m_yG@(?0d&12UaYJUCR$o+i?pYIb$xc>9;lQe;mo| z{5H|2yU=H=nI_%H4mmS<2-kA{EW` zIpGEwZv&^bzy5f!dCpkc$7}Ylb)S?(Z+w@A-K0^Nz1l>@F3|qSi3hhj zT|u+&dGt1w%9v*QLTmylluIH%QQztgDimS8^cM5FN)Egoxh`(3<4!U{OgI)LG zL?3*+#P|ZGmrf1_C#Z$eKZ}drv({ONV*L?sw?=mo4MGh{4yAJI{Lu zBviD!YS?Ux$KH8Uz76N7vdIR2U88Yo&m0X-+~y-lCz3|pwz%pDQiKy!#zo0g6PtWy z80!Jc-H}U1#MddW?PSR=AM}IYs&kY{7Z*;?X5GhPWVFVwJcsU(tu+uHBD+H&8lsw= zx&4`QNGKypPUpV-Jky<$4kcV6Uz+a^3zyfb(v2J!RMfh_V%`IRk_c-jJH?o(&Zg(l zh-RW{(_rrC!64>xA>OtUwxl{sn1+}S-v&hBGO55`pgw0pBRpYO1CbCvE|Wo56e&lj zW4fQQK>FuVg5a217b^=3*#8`l58EtQkyh0Xh>HoZxKjNBaD>D6^R_zdwr5Xt+((1m z(N7s@u|DrfLFy}|zwpq-B(1rLt)&ViSt2A0G1Cg!DU=E(uCJsf)5pcOQJh0?(NGy-p*i*;$WV>X<8!J~a#F zrqfPd5PTxEvQ#z;UeVQPlvhl*c_2xHRlB54>B?0n{rE-1CCTw|G|i>2- z8ZuPF?#8>FB174Wp7RxBPl_y*sW2S5;SX-KD`(C5W!FbZ7L*v(hhhYL3B4UBCBFzV zClLf0;Vyh5OgxbHqsv)Jjf<@KKDlb1XIiiO%vg&0W{CHEMr7{DkoKed=UEBU<|drw zAAp~t$+wPKWPKomb#B_p_ViNTlj!8zZR7$AE|>_GVUadka%8ym?*CjaT2J zXTXsD*3Hy`WzcDOp(uo!#Z8>ADY>gXIj=qAh&lpXXNP?FNxQOSOG{MW0^q+W$xi~;S zx*60z6u2wCA+Zf3abR&e{IAlux=dJx0Nyv@uQgo7O-62Cr<=GmTGqUlzquQq(`vl3 zy(&PyhoF~kXBRSit?kRjmxC&wtA`;jHL!3GM0Y(hs0`#yJ!_)c$IuuwO~k@<-B7!+ z9Oim~w8CPY*Uz6o)%NLigfEnvW>AG4W!PU6OjZ=7qvqd{)3@-E=eVaFuFRo=HFI}> zo?-;#?jQ1&|7YI*bG>to?p6=tC2G2rzHGE#9v-egm>{G;$lRp1dDFzt(hyM+m{XOA zMc;b;HJKLA)|F~_SI0K^>+U{M%B%N*WS}d_UUY_59rY&pV1a{*l%`A3Ec7V~ZEeyO z)Zp#$NqLeO-Y?>&i)B!Tjla47QaT5++f#xRppc_WEP7DL)N00M-YvZH_-(wSqp#pu zu5-lOW{yVjFgl0E&0T^QpCk|HwF~7qn z+CQ5ZDuI=_ilVGVu95Iq@< zfVwz7t5zH((r?}JBi<*5x_=opf&Y)%|AnZbp;LnYByZv$XM0`{TEM8t4k+6Nj!=1Q)~Lgo#R8D)?!XVl>%G)Pj`-$nP0Ge&oKDZ z(YP=HDeO>h_~lT)w?0jb*$?ag5u#rehm)sCIf-#{4m^(7q5kmCxc|CVp9CeuK04NK zmTcghKLq!`6W~uP=1DkhU>X8gayhYZ0ysaG&C@uCZk?WQPvX!49{`+_r#xc*v`4>Z zp5Rp6+zEan{T%Lo3E}b4PLFjbT`K