From 02f7b725173fa2292f0daee443918622f1fb8b34 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 8 Apr 2008 10:07:36 +0000 Subject: [PATCH] Merged revisions 638786-638802,638805-638811,638813-638814,638816-639230,639233-639241,639243-639253,639255-639486,639488-639601,639603-639835,639837-639917,639919-640056,640058-640710,640712-641156,641158-641184,641186-641795,641797-641798,641800-641933,641935-641963,641965-641966,641968-641995,641997-642230,642232-642562,642564-642565,642568-642570,642572-642573,642576-642736,642739-642877,642879,642881-642890,642892-642903,642905-642945,642947-643624,643626-643653,643655-643669,643671,643673-643830,643832-643833,643835-644342,644344-644472,644474-644508,644510-645347,645349-645351,645353-645821 via svnmerge from https://svn.apache.org:443/repos/asf/poi/trunk ........ r645560 | nick | 2008-04-07 16:20:57 +0100 (Mon, 07 Apr 2008) | 1 line Fix 43670, 44501 - Fix how HDGF deals with trailing data in the list of chunk headers ........ r645566 | yegor | 2008-04-07 16:34:53 +0100 (Mon, 07 Apr 2008) | 1 line empty.ppt with the master styles compatible with PPT 2003 ........ r645567 | yegor | 2008-04-07 16:35:49 +0100 (Mon, 07 Apr 2008) | 1 line misc usermodel improvements in HSLF ........ git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@645823 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../apache/poi/hdgf/chunks/ChunkHeader.java | 28 +- .../poi/hdgf/chunks/ChunkHeaderV4V5.java | 56 + .../apache/poi/hdgf/chunks/ChunkHeaderV6.java | 6 +- .../hdgf/extractor/VisioTextExtractor.java | 11 +- .../apache/poi/hdgf/streams/ChunkStream.java | 16 +- .../src/org/apache/poi/hslf/data/empty.ppt | Bin 10240 -> 10240 bytes .../apache/poi/hslf/model/PPGraphics2D.java | 1801 ++++++++++++++--- .../org/apache/poi/hslf/model/Polygon.java | 160 ++ .../src/org/apache/poi/hslf/model/Shape.java | 80 +- .../org/apache/poi/hslf/model/ShapeGroup.java | 15 +- .../apache/poi/hslf/model/SimpleShape.java | 20 +- .../org/apache/poi/hdgf/data/44594-2.vsd | Bin 0 -> 69632 bytes .../org/apache/poi/hdgf/data/44594.vsd | Bin 0 -> 54272 bytes .../hdgf/extractor/TestVisioExtractor.java | 41 +- .../poi/hdgf/streams/TestStreamBugs.java | 102 + .../hslf/record/TestTxMasterStyleAtom.java | 6 +- 18 files changed, 2039 insertions(+), 305 deletions(-) create mode 100644 src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV4V5.java create mode 100755 src/scratchpad/src/org/apache/poi/hslf/model/Polygon.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hdgf/data/44594-2.vsd create mode 100644 src/scratchpad/testcases/org/apache/poi/hdgf/data/44594.vsd create mode 100644 src/scratchpad/testcases/org/apache/poi/hdgf/streams/TestStreamBugs.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index f214e05e12..84c1156af9 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 43670, 44501 - Fix how HDGF deals with trailing data in the list of chunk headers 30311 - More work on Conditional Formatting refactored all junits' usage of HSSF.testdata.path to one place 44739 - Small fixes for conditional formatting (regions with max row/col index) diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 7bdc284dad..bb7394dd33 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 43670, 44501 - Fix how HDGF deals with trailing data in the list of chunk headers 30311 - More work on Conditional Formatting refactored all junits' usage of HSSF.testdata.path to one place 44739 - Small fixes for conditional formatting (regions with max row/col index) diff --git a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeader.java b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeader.java index 0d17669c28..88c91c74f9 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeader.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeader.java @@ -47,10 +47,32 @@ public abstract class ChunkHeader { ch.unknown3 = (short)LittleEndian.getUnsignedByte(data, offset + 18); return ch; - } else if(documentVersion == 5) { - throw new RuntimeException("TODO"); + } else if(documentVersion == 5 || documentVersion == 4) { + ChunkHeaderV4V5 ch = new ChunkHeaderV4V5(); + + ch.type = (int)LittleEndian.getShort(data, offset + 0); + ch.id = (int)LittleEndian.getShort(data, offset + 2); + ch.unknown2 = (short)LittleEndian.getUnsignedByte(data, offset + 4); + ch.unknown3 = (short)LittleEndian.getUnsignedByte(data, offset + 5); + ch.unknown1 = (short)LittleEndian.getShort(data, offset + 6); + ch.length = (int)LittleEndian.getUInt(data, offset + 8); + + return ch; + } else { + throw new IllegalArgumentException("Visio files with versions below 4 are not supported, yours was " + documentVersion); + } + } + + /** + * Returns the size of a chunk header for the given document version. + */ + public static int getHeaderSize(int documentVersion) { + if(documentVersion > 6) { + return ChunkHeaderV11.getHeaderSize(); + } else if(documentVersion == 6) { + return ChunkHeaderV6.getHeaderSize(); } else { - throw new IllegalArgumentException("Visio files with versions below 5 are not supported, yours was " + documentVersion); + return ChunkHeaderV4V5.getHeaderSize(); } } diff --git a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV4V5.java b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV4V5.java new file mode 100644 index 0000000000..335c9c543e --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV4V5.java @@ -0,0 +1,56 @@ +/* ==================================================================== + 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.hdgf.chunks; + +/** + * A chunk header from v4 or v5 + */ +public class ChunkHeaderV4V5 extends ChunkHeader { + protected short unknown2; + protected short unknown3; + + public short getUnknown2() { + return unknown2; + } + public short getUnknown3() { + return unknown3; + } + + protected static int getHeaderSize() { + return 12; + } + + public int getSizeInBytes() { + return getHeaderSize(); + } + + /** + * Does the chunk have a trailer? + */ + public boolean hasTrailer() { + // V4 and V5 never has trailers + return false; + } + + /** + * Does the chunk have a separator? + */ + public boolean hasSeparator() { + // V4 and V5 never has separators + return false; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV6.java b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV6.java index e8061563af..4d13b85bd2 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV6.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV6.java @@ -30,9 +30,13 @@ public class ChunkHeaderV6 extends ChunkHeader { return unknown3; } - public int getSizeInBytes() { + protected static int getHeaderSize() { + // Looks like it ought to be 19... return 19; } + public int getSizeInBytes() { + return getHeaderSize(); + } /** * Does the chunk have a trailer? diff --git a/src/scratchpad/src/org/apache/poi/hdgf/extractor/VisioTextExtractor.java b/src/scratchpad/src/org/apache/poi/hdgf/extractor/VisioTextExtractor.java index a7857e46f2..034714c7bc 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/extractor/VisioTextExtractor.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/extractor/VisioTextExtractor.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import org.apache.poi.POITextExtractor; import org.apache.poi.hdgf.HDGFDiagram; +import org.apache.poi.hdgf.chunks.Chunk; import org.apache.poi.hdgf.chunks.Chunk.Command; import org.apache.poi.hdgf.streams.ChunkStream; import org.apache.poi.hdgf.streams.PointerContainingStream; @@ -71,11 +72,13 @@ public class VisioTextExtractor extends POITextExtractor { if(stream instanceof ChunkStream) { ChunkStream cs = (ChunkStream)stream; for(int i=0; i 0) { // First command - Command cmd = cs.getChunks()[i].getCommands()[0]; + Command cmd = chunk.getCommands()[0]; if(cmd != null && cmd.getValue() != null) { text.add( cmd.getValue().toString() ); } diff --git a/src/scratchpad/src/org/apache/poi/hdgf/streams/ChunkStream.java b/src/scratchpad/src/org/apache/poi/hdgf/streams/ChunkStream.java index a59fe43ff9..65b69d95ad 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/streams/ChunkStream.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/streams/ChunkStream.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import org.apache.poi.hdgf.chunks.Chunk; import org.apache.poi.hdgf.chunks.ChunkFactory; +import org.apache.poi.hdgf.chunks.ChunkHeader; import org.apache.poi.hdgf.pointers.Pointer; public class ChunkStream extends Stream { @@ -51,10 +52,17 @@ public class ChunkStream extends Stream { int pos = 0; byte[] contents = getStore().getContents(); while(pos < contents.length) { - Chunk chunk = chunkFactory.createChunk(contents, pos); - chunksA.add(chunk); - - pos += chunk.getOnDiskSize(); + // Ensure we have enough data to create a chunk from + int headerSize = ChunkHeader.getHeaderSize(chunkFactory.getVersion()); + if(pos+headerSize <= contents.length) { + Chunk chunk = chunkFactory.createChunk(contents, pos); + chunksA.add(chunk); + + pos += chunk.getOnDiskSize(); + } else { + System.err.println("Needed " + headerSize + " bytes to create the next chunk header, but only found " + (contents.length-pos) + " bytes, ignoring rest of data"); + pos = contents.length; + } } chunks = (Chunk[])chunksA.toArray(new Chunk[chunksA.size()]); diff --git a/src/scratchpad/src/org/apache/poi/hslf/data/empty.ppt b/src/scratchpad/src/org/apache/poi/hslf/data/empty.ppt index 23e1e94ca8a5d1888d35fd89f307c4b01e57e059..20d2398e39f63764870e4548c7b7e3ffa50fd6db 100644 GIT binary patch literal 10240 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 literal 10240 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 aQu;?O3{w$QsN?m`_&A!)Sk;en|NjRt)FYw* diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java index bc01d9970f..4aad44d75d 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java @@ -21,25 +21,28 @@ import java.awt.*; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; +import java.awt.font.TextLayout; import java.awt.image.*; import java.awt.image.renderable.RenderableImage; -import java.awt.geom.AffineTransform; -import java.awt.geom.PathIterator; +import java.awt.geom.*; import java.text.AttributedCharacterIterator; import java.util.Map; import java.util.ArrayList; - -import org.apache.poi.ddf.EscherProperties; import org.apache.poi.hslf.usermodel.RichTextRun; import org.apache.poi.hslf.exceptions.HSLFException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * Translates Graphics2D calls into PowerPoint. * * @author Yegor Kozlov */ -public class PPGraphics2D extends Graphics2D { - //The group to write the graphics calls into. +public class PPGraphics2D extends Graphics2D implements Cloneable { + + private static final Log log = LogFactory.getLog(PPGraphics2D.class); + + //The ppt object to write into. private ShapeGroup group; private AffineTransform transform; @@ -47,9 +50,14 @@ public class PPGraphics2D extends Graphics2D { private Paint paint; private Font font; private Color foreground; - private Color background = Color.white; - private Shape clip; - int count = 0; + private Color background; + private RenderingHints hints; + + /** + * the maximum distance that the line segments used to approximate the curved segments + */ + public static final float FLATNESS = 0.1f; + /** * Construct Java Graphics object which translates graphic calls in ppt drawing layer. * @@ -57,7 +65,14 @@ public class PPGraphics2D extends Graphics2D { */ public PPGraphics2D(ShapeGroup group){ this.group = group; + transform = new AffineTransform(); + stroke = new BasicStroke(); + paint = Color.black; + font = new Font("Arial", Font.PLAIN, 12); + background = Color.black; + foreground = Color.white; + hints = new RenderingHints(null); } /** @@ -67,58 +82,144 @@ public class PPGraphics2D extends Graphics2D { return group; } + /** + * Gets the current font. + * @return this graphics context's current font. + * @see java.awt.Font + * @see java.awt.Graphics#setFont(Font) + */ public Font getFont(){ return font; } + /** + * Sets this graphics context's font to the specified font. + * All subsequent text operations using this graphics context + * use this font. + * @param font the font. + * @see java.awt.Graphics#getFont + * @see java.awt.Graphics#drawString(java.lang.String, int, int) + * @see java.awt.Graphics#drawBytes(byte[], int, int, int, int) + * @see java.awt.Graphics#drawChars(char[], int, int, int, int) + */ public void setFont(Font font){ this.font = font; } - public Color getColor(){ + /** + * Gets this graphics context's current color. + * @return this graphics context's current color. + * @see java.awt.Color + * @see java.awt.Graphics#setColor + */ + public Color getColor(){ return foreground; } - public void setColor(Color color) { - this.foreground = color; + /** + * Sets this graphics context's current color to the specified + * color. All subsequent graphics operations using this graphics + * context use this specified color. + * @param c the new rendering color. + * @see java.awt.Color + * @see java.awt.Graphics#getColor + */ + public void setColor(Color c) { + setPaint(c); } + /** + * Returns the current Stroke in the + * Graphics2D context. + * @return the current Graphics2D Stroke, + * which defines the line style. + * @see #setStroke + */ public Stroke getStroke(){ return stroke; } + /** + * Sets the Stroke for the Graphics2D context. + * @param s the Stroke object to be used to stroke a + * Shape during the rendering process + */ public void setStroke(Stroke s){ this.stroke = s; } + /** + * Returns the current Paint of the + * Graphics2D context. + * @return the current Graphics2D Paint, + * which defines a color or pattern. + * @see #setPaint + * @see java.awt.Graphics#setColor + */ public Paint getPaint(){ return paint; } - public void setPaint(Paint paint){ + /** + * Sets the Paint attribute for the + * Graphics2D context. Calling this method + * with a null Paint object does + * not have any effect on the current Paint attribute + * of this Graphics2D. + * @param paint the Paint object to be used to generate + * color during the rendering process, or null + * @see java.awt.Graphics#setColor + */ + public void setPaint(Paint paint){ + if(paint == null) return; + this.paint = paint; - if (paint instanceof Color) setColor((Color)paint); + if (paint instanceof Color) foreground = (Color)paint; } + /** + * Returns a copy of the current Transform in the + * Graphics2D context. + * @return the current AffineTransform in the + * Graphics2D context. + * @see #transform + * @see #setTransform + */ public AffineTransform getTransform(){ - return (AffineTransform)transform.clone(); + return new AffineTransform(transform); } - public void setTransform(AffineTransform trans) { - transform = (AffineTransform)trans.clone(); + /** + * Sets the Transform in the Graphics2D + * context. + * @param Tx the AffineTransform object to be used in the + * rendering process + * @see #transform + * @see AffineTransform + */ + public void setTransform(AffineTransform Tx) { + transform = new AffineTransform(Tx); } + /** + * Strokes the outline of a Shape using the settings of the + * current Graphics2D context. The rendering attributes + * applied include the Clip, Transform, + * Paint, Composite and + * Stroke attributes. + * @param shape the Shape to be rendered + * @see #setStroke + * @see #setPaint + * @see java.awt.Graphics#setColor + * @see #transform + * @see #setTransform + * @see #clip + * @see #setClip + * @see #setComposite + */ public void draw(Shape shape){ - if(clip != null) { - java.awt.Rectangle bounds = getTransform().createTransformedShape(shape).getBounds(); - if (bounds.width == 0) bounds.width = 1; - if (bounds.height == 0) bounds.height = 1; - if (!clip.getBounds().contains(bounds)) { - return; - } - } - PathIterator it = shape.getPathIterator(transform); + PathIterator it = shape.getPathIterator(transform, FLATNESS); double[] prev = null; double[] coords = new double[6]; double[] first = new double[6]; @@ -127,17 +228,12 @@ public class PPGraphics2D extends Graphics2D { int type = it.currentSegment(coords); if (prev != null ){ Line line = new Line(group); - if (stroke instanceof BasicStroke){ - BasicStroke bs = (BasicStroke)stroke; - line.setLineWidth(bs.getLineWidth()); - float[] dash = bs.getDashArray(); - if (dash != null) line.setLineDashing(Line.PEN_DASH); - } - if(getColor() != null) line.setLineColor(getColor()); + applyPaint(line); + applyStroke(line); if (type == PathIterator.SEG_LINETO) { - line.setAnchor(new java.awt.Rectangle((int)prev[0], (int)prev[1], (int)(coords[0] - prev[0]), (int)(coords[1] - prev[1]))); + line.setAnchor(new Rectangle2D.Double(prev[0], prev[1], (coords[0] - prev[0]), (coords[1] - prev[1]))); } else if (type == PathIterator.SEG_CLOSE){ - line.setAnchor(new java.awt.Rectangle((int)coords[0], (int)coords[1], (int)(first[0] - coords[0]), (int)(first[1] - coords[1]))); + line.setAnchor(new Rectangle2D.Double(coords[0], coords[1], (first[0] - coords[0]), (first[1] - coords[1]))); } group.addShape(line); } @@ -147,368 +243,1589 @@ public class PPGraphics2D extends Graphics2D { } - public void drawString(String string, float x, float y){ - TextBox txt = new TextBox(group); - txt.getTextRun().supplySlideShow(group.getSheet().getSlideShow()); - txt.getTextRun().setSheet(group.getSheet()); - txt.setText(string); + /** + * Renders the text specified by the specified String, + * using the current text attribute state in the Graphics2D context. + * The baseline of the first character is at position + * (xy) in the User Space. + * The rendering attributes applied include the Clip, + * Transform, Paint, Font and + * Composite attributes. For characters in script systems + * such as Hebrew and Arabic, the glyphs can be rendered from right to + * left, in which case the coordinate supplied is the location of the + * leftmost character on the baseline. + * @param s the String to be rendered + * @param x the x coordinate of the location where the + * String should be rendered + * @param y the y coordinate of the location where the + * String should be rendered + * @throws NullPointerException if str is + * null + * @see #setPaint + * @see java.awt.Graphics#setColor + * @see java.awt.Graphics#setFont + * @see #setTransform + * @see #setComposite + * @see #setClip + */ + public void drawString(String s, float x, float y) { + TextBox txt = new TextBox(group); + txt.getTextRun().supplySlideShow(group.getSheet().getSlideShow()); + txt.getTextRun().setSheet(group.getSheet()); + txt.setText(s); - RichTextRun rt = txt.getTextRun().getRichTextRuns()[0]; - rt.setFontSize(font.getSize()); - rt.setFontName(font.getFamily()); + RichTextRun rt = txt.getTextRun().getRichTextRuns()[0]; + rt.setFontSize(font.getSize()); + rt.setFontName(font.getFamily()); - if(getColor() != null) rt.setFontColor(getColor()); + if (getColor() != null) rt.setFontColor(getColor()); if (font.isBold()) rt.setBold(true); if (font.isItalic()) rt.setItalic(true); - txt.setMarginBottom(0); - txt.setMarginTop(0); - txt.setMarginLeft(0); - txt.setMarginRight(0); - txt.setWordWrap(TextBox.WrapNone); + txt.setMarginBottom(0); + txt.setMarginTop(0); + txt.setMarginLeft(0); + txt.setMarginRight(0); + txt.setWordWrap(TextBox.WrapNone); + txt.setHorizontalAlignment(TextBox.AlignLeft); + txt.setVerticalAlignment(TextBox.AnchorMiddle); + - if (!"".equals(string)) txt.resizeToFitText(); - int height = (int)txt.getAnchor().getHeight(); + TextLayout layout = new TextLayout(s, font, getFontRenderContext()); + float ascent = layout.getAscent(); - /* - In powerpoint anchor of a shape is its top left corner. - Java graphics sets string coordinates by the baseline of the first character - so we need to shift down by the height of the textbox + float width = (float) Math.floor(layout.getAdvance()); + /** + * Even if top and bottom margins are set to 0 PowerPoint + * always sets extra space between the text and its bounding box. + * + * Approximation height = ascent*2 works good enough in most cases */ - txt.moveTo((int)x, (int)(y - height)); + float height = ascent * 2; - if(clip != null) { - if (!clip.getBounds().contains(txt.getAnchor())) { - ;//return; - } - } - group.addShape(txt); + /* + In powerpoint anchor of a shape is its top left corner. + Java graphics sets string coordinates by the baseline of the first character + so we need to shift up by the height of the textbox + */ + y -= height / 2 + ascent / 2; + + /* + In powerpoint anchor of a shape is its top left corner. + Java graphics sets string coordinates by the baseline of the first character + so we need to shift down by the height of the textbox + */ + txt.setAnchor(new Rectangle2D.Float(x, y, width, height)); + + group.addShape(txt); } + /** + * Fills the interior of a Shape using the settings of the + * Graphics2D context. The rendering attributes applied + * include the Clip, Transform, + * Paint, and Composite. + * @param shape the Shape to be filled + * @see #setPaint + * @see java.awt.Graphics#setColor + * @see #transform + * @see #setTransform + * @see #setComposite + * @see #clip + * @see #setClip + */ public void fill(Shape shape){ - if(clip != null) { - java.awt.Rectangle bounds = getTransform().createTransformedShape(shape).getBounds(); - if (bounds.width == 0) bounds.width = 1; - if (bounds.height == 0) bounds.height = 1; - if (!clip.getBounds().contains(bounds)) { - return; - } - } - PathIterator it = shape.getPathIterator(transform); + PathIterator it = shape.getPathIterator(transform, FLATNESS); ArrayList pnt = new ArrayList(); double[] coords = new double[6]; while(!it.isDone()){ int type = it.currentSegment(coords); if (type != PathIterator.SEG_CLOSE) { - pnt.add(new Point((int)coords[0], (int)coords[1])); + pnt.add(new Point2D.Double(coords[0], coords[1])); } it.next(); } - int[] xPoints= new int[pnt.size()]; - int[] yPoints= new int[pnt.size()]; - for (int i = 0; i < pnt.size(); i++) { - Point p = (Point)pnt.get(i); - xPoints[i] = p.x; - yPoints[i] = p.y; - } + if(pnt.size() > 0){ + Point2D[] points = (Point2D[])pnt.toArray(new Point2D[pnt.size()]); + Polygon p = new Polygon(group); + p.setPoints(points); + applyPaint(p); - AutoShape r = new AutoShape(ShapeTypes.Rectangle); - if (paint instanceof Color){ - Color color = (Color)paint; - r.setFillColor(color); - } - if(getColor() != null) r.setLineColor(getColor()); - if (stroke instanceof BasicStroke){ - BasicStroke bs = (BasicStroke)stroke; - r.setLineWidth(bs.getLineWidth()); - float[] dash = bs.getDashArray(); - if (dash != null) r.setLineDashing(Line.PEN_DASH); - } + p.setLineColor(null); //Fills must be "No Line" - java.awt.Rectangle bounds = transform.createTransformedShape(shape).getBounds(); - r.setAnchor(bounds); - group.addShape(r); + Rectangle2D bounds = transform.createTransformedShape(shape).getBounds2D(); + p.setAnchor(bounds); + group.addShape(p); + } } - public void translate(int x, int y) { - AffineTransform at = new AffineTransform(); - at.translate(x, y); - transform.concatenate(at); + /** + * Translates the origin of the graphics context to the point + * (xy) in the current coordinate system. + * Modifies this graphics context so that its new origin corresponds + * to the point (xy) in this graphics context's + * original coordinate system. All coordinates used in subsequent + * rendering operations on this graphics context will be relative + * to this new origin. + * @param x the x coordinate. + * @param y the y coordinate. + */ + public void translate(int x, int y){ + transform.translate(x, y); } - public void clip(Shape shape) { - this.clip = transform.createTransformedShape(shape); - //update size of the escher group which holds the drawing - group.setAnchor(clip.getBounds()); + /** + * Intersects the current Clip with the interior of the + * specified Shape and sets the Clip to the + * resulting intersection. The specified Shape is + * transformed with the current Graphics2D + * Transform before being intersected with the current + * Clip. This method is used to make the current + * Clip smaller. + * To make the Clip larger, use setClip. + * The user clip modified by this method is independent of the + * clipping associated with device bounds and visibility. If no clip has + * previously been set, or if the clip has been cleared using + * {@link java.awt.Graphics#setClip(Shape) setClip} with a + * null argument, the specified Shape becomes + * the new user clip. + * @param s the Shape to be intersected with the current + * Clip. If s is null, + * this method clears the current Clip. + */ + public void clip(Shape s){ + log.warn("Not implemented"); } - public Shape getClip() { - return clip; + /** + * Gets the current clipping area. + * This method returns the user clip, which is independent of the + * clipping associated with device bounds and window visibility. + * If no clip has previously been set, or if the clip has been + * cleared using setClip(null), this method returns + * null. + * @return a Shape object representing the + * current clipping area, or null if + * no clip is set. + * @see java.awt.Graphics#getClipBounds() + * @see java.awt.Graphics#clipRect + * @see java.awt.Graphics#setClip(int, int, int, int) + * @see java.awt.Graphics#setClip(Shape) + * @since JDK1.1 + */ + public Shape getClip(){ + log.warn("Not implemented"); + return null; } - public void scale(double sx, double sy) { - AffineTransform at = new AffineTransform(); - at.scale(sx, sy); - transform.concatenate(at); - } - //=============================================== - public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { - throw new HSLFException("Not implemented"); + /** + * Concatenates the current Graphics2D + * Transform with a scaling transformation + * Subsequent rendering is resized according to the specified scaling + * factors relative to the previous scaling. + * This is equivalent to calling transform(S), where S is an + * AffineTransform represented by the following matrix: + *
+     *          [   sx   0    0   ]
+     *          [   0    sy   0   ]
+     *          [   0    0    1   ]
+     * 
+ * @param sx the amount by which X coordinates in subsequent + * rendering operations are multiplied relative to previous + * rendering operations. + * @param sy the amount by which Y coordinates in subsequent + * rendering operations are multiplied relative to previous + * rendering operations. + */ + public void scale(double sx, double sy){ + transform.scale(sx, sy); } - public void drawString(String str, int x, int y) { - throw new HSLFException("Not implemented"); - } + /** + * Draws an outlined round-cornered rectangle using this graphics + * context's current color. The left and right edges of the rectangle + * are at x and x + width, + * respectively. The top and bottom edges of the rectangle are at + * y and y + height. + * @param x the x coordinate of the rectangle to be drawn. + * @param y the y coordinate of the rectangle to be drawn. + * @param width the width of the rectangle to be drawn. + * @param height the height of the rectangle to be drawn. + * @param arcWidth the horizontal diameter of the arc + * at the four corners. + * @param arcHeight the vertical diameter of the arc + * at the four corners. + * @see java.awt.Graphics#fillRoundRect + */ + public void drawRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight){ + AutoShape shape = new AutoShape(ShapeTypes.RoundRectangle, group); + shape.setFillColor(null); + applyStroke(shape); + shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); + group.addShape(shape); + } - public void fillOval(int x, int y, int width, int height) { - throw new HSLFException("Not implemented"); + /** + * Draws the text given by the specified string, using this + * graphics context's current font and color. The baseline of the + * first character is at position (xy) in this + * graphics context's coordinate system. + * @param str the string to be drawn. + * @param x the x coordinate. + * @param y the y coordinate. + * @see java.awt.Graphics#drawBytes + * @see java.awt.Graphics#drawChars + */ + public void drawString(String str, int x, int y){ + drawString(str, (float)x, (float)y); } - public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { - throw new HSLFException("Not implemented"); + /** + * Fills an oval bounded by the specified rectangle with the + * current color. + * @param x the x coordinate of the upper left corner + * of the oval to be filled. + * @param y the y coordinate of the upper left corner + * of the oval to be filled. + * @param width the width of the oval to be filled. + * @param height the height of the oval to be filled. + * @see java.awt.Graphics#drawOval + */ + public void fillOval(int x, int y, int width, int height){ + AutoShape shape = new AutoShape(ShapeTypes.Ellipse, group); + applyPaint(shape); + applyStroke(shape); + shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); + group.addShape(shape); } - public void fillArc(int x, int y, int width, int height, - int startAngle, int arcAngle) { - throw new HSLFException("Not implemented"); + /** + * Fills the specified rounded corner rectangle with the current color. + * The left and right edges of the rectangle + * are at x and x + width - 1, + * respectively. The top and bottom edges of the rectangle are at + * y and y + height - 1. + * @param x the x coordinate of the rectangle to be filled. + * @param y the y coordinate of the rectangle to be filled. + * @param width the width of the rectangle to be filled. + * @param height the height of the rectangle to be filled. + * @param arcWidth the horizontal diameter + * of the arc at the four corners. + * @param arcHeight the vertical diameter + * of the arc at the four corners. + * @see java.awt.Graphics#drawRoundRect + */ + public void fillRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight){ + AutoShape shape = new AutoShape(ShapeTypes.RoundRectangle, group); + applyPaint(shape); + applyStroke(shape); + shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); + group.addShape(shape); } - public void setPaintMode() { - throw new HSLFException("Not implemented"); + /** + * Fills a circular or elliptical arc covering the specified rectangle. + *

+ * The resulting arc begins at startAngle and extends + * for arcAngle degrees. + * Angles are interpreted such that 0 degrees + * is at the 3 o'clock position. + * A positive value indicates a counter-clockwise rotation + * while a negative value indicates a clockwise rotation. + *

+ * The center of the arc is the center of the rectangle whose origin + * is (xy) and whose size is specified by the + * width and height arguments. + *

+ * The resulting arc covers an area + * width + 1 pixels wide + * by height + 1 pixels tall. + *

+ * The angles are specified relative to the non-square extents of + * the bounding rectangle such that 45 degrees always falls on the + * line from the center of the ellipse to the upper right corner of + * the bounding rectangle. As a result, if the bounding rectangle is + * noticeably longer in one axis than the other, the angles to the + * start and end of the arc segment will be skewed farther along the + * longer axis of the bounds. + * @param x the x coordinate of the + * upper-left corner of the arc to be filled. + * @param y the y coordinate of the + * upper-left corner of the arc to be filled. + * @param width the width of the arc to be filled. + * @param height the height of the arc to be filled. + * @param startAngle the beginning angle. + * @param arcAngle the angular extent of the arc, + * relative to the start angle. + * @see java.awt.Graphics#drawArc + */ + public void fillArc(int x, int y, int width, int height, + int startAngle, int arcAngle){ + AutoShape shape = new AutoShape(ShapeTypes.Arc, group); + applyPaint(shape); + applyStroke(shape); + shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); + group.addShape(shape); } + /** + * Draws the outline of a circular or elliptical arc + * covering the specified rectangle. + *

+ * The resulting arc begins at startAngle and extends + * for arcAngle degrees, using the current color. + * Angles are interpreted such that 0 degrees + * is at the 3 o'clock position. + * A positive value indicates a counter-clockwise rotation + * while a negative value indicates a clockwise rotation. + *

+ * The center of the arc is the center of the rectangle whose origin + * is (xy) and whose size is specified by the + * width and height arguments. + *

+ * The resulting arc covers an area + * width + 1 pixels wide + * by height + 1 pixels tall. + *

+ * The angles are specified relative to the non-square extents of + * the bounding rectangle such that 45 degrees always falls on the + * line from the center of the ellipse to the upper right corner of + * the bounding rectangle. As a result, if the bounding rectangle is + * noticeably longer in one axis than the other, the angles to the + * start and end of the arc segment will be skewed farther along the + * longer axis of the bounds. + * @param x the x coordinate of the + * upper-left corner of the arc to be drawn. + * @param y the y coordinate of the + * upper-left corner of the arc to be drawn. + * @param width the width of the arc to be drawn. + * @param height the height of the arc to be drawn. + * @param startAngle the beginning angle. + * @param arcAngle the angular extent of the arc, + * relative to the start angle. + * @see java.awt.Graphics#fillArc + */ public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { - throw new HSLFException("Not implemented"); - } - - - public void drawPolyline(int xPoints[], int yPoints[], - int nPoints) { - throw new HSLFException("Not implemented"); + AutoShape shape = new AutoShape(ShapeTypes.Arc, group); + shape.setFillColor(null); + applyStroke(shape); + shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); + group.addShape(shape); } - public Graphics create() { - throw new HSLFException("Not implemented"); - } - public void drawOval(int x, int y, int width, int height) { - AutoShape ellipse = new AutoShape(ShapeTypes.Ellipse); - ellipse.setAnchor(new java.awt.Rectangle(x-width/2, y-height/2, width, height)); - if (stroke instanceof BasicStroke){ - BasicStroke bs = (BasicStroke)stroke; - ellipse.setLineWidth(bs.getLineWidth()); - } - if(getColor() != null) ellipse.setLineColor(getColor()); - if (paint instanceof Color){ - Color color = (Color)paint; - ellipse.setFillColor(color); + /** + * Draws a sequence of connected lines defined by + * arrays of x and y coordinates. + * Each pair of (xy) coordinates defines a point. + * The figure is not closed if the first point + * differs from the last point. + * @param xPoints an array of x points + * @param yPoints an array of y points + * @param nPoints the total number of points + * @see java.awt.Graphics#drawPolygon(int[], int[], int) + * @since JDK1.1 + */ + public void drawPolyline(int[] xPoints, int[] yPoints, + int nPoints){ + if(nPoints > 0){ + GeneralPath path = new GeneralPath(); + path.moveTo(xPoints[0], yPoints[0]); + for(int i=1; ix, y, + * width, and height arguments. + *

+ * The oval covers an area that is + * width + 1 pixels wide + * and height + 1 pixels tall. + * @param x the x coordinate of the upper left + * corner of the oval to be drawn. + * @param y the y coordinate of the upper left + * corner of the oval to be drawn. + * @param width the width of the oval to be drawn. + * @param height the height of the oval to be drawn. + * @see java.awt.Graphics#fillOval + */ + public void drawOval(int x, int y, int width, int height){ + AutoShape shape = new AutoShape(ShapeTypes.Ellipse, group); + shape.setFillColor(null); + applyStroke(shape); + shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); + group.addShape(shape); } - + /** + * Draws as much of the specified image as is currently available. + * The image is drawn with its top-left corner at + * (xy) in this graphics context's coordinate + * space. Transparent pixels are drawn in the specified + * background color. + *

+ * This operation is equivalent to filling a rectangle of the + * width and height of the specified image with the given color and then + * drawing the image on top of it, but possibly more efficient. + *

+ * This method returns immediately in all cases, even if the + * complete image has not yet been loaded, and it has not been dithered + * and converted for the current output device. + *

+ * If the image has not yet been completely loaded, then + * drawImage returns false. As more of + * the image becomes available, the process that draws the image notifies + * the specified image observer. + * @param img the specified image to be drawn. + * @param x the x coordinate. + * @param y the y coordinate. + * @param bgcolor the background color to paint under the + * non-opaque portions of the image. + * @param observer object to be notified as more of + * the image is converted. + * @see java.awt.Image + * @see java.awt.image.ImageObserver + * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) + */ public boolean drawImage(Image img, int x, int y, Color bgcolor, - ImageObserver observer) { - throw new HSLFException("Not implemented"); + ImageObserver observer){ + log.warn("Not implemented"); + + return false; } + /** + * Draws as much of the specified image as has already been scaled + * to fit inside the specified rectangle. + *

+ * The image is drawn inside the specified rectangle of this + * graphics context's coordinate space, and is scaled if + * necessary. Transparent pixels are drawn in the specified + * background color. + * This operation is equivalent to filling a rectangle of the + * width and height of the specified image with the given color and then + * drawing the image on top of it, but possibly more efficient. + *

+ * This method returns immediately in all cases, even if the + * entire image has not yet been scaled, dithered, and converted + * for the current output device. + * If the current output representation is not yet complete then + * drawImage returns false. As more of + * the image becomes available, the process that draws the image notifies + * the specified image observer. + *

+ * A scaled version of an image will not necessarily be + * available immediately just because an unscaled version of the + * image has been constructed for this output device. Each size of + * the image may be cached separately and generated from the original + * data in a separate image production sequence. + * @param img the specified image to be drawn. + * @param x the x coordinate. + * @param y the y coordinate. + * @param width the width of the rectangle. + * @param height the height of the rectangle. + * @param bgcolor the background color to paint under the + * non-opaque portions of the image. + * @param observer object to be notified as more of + * the image is converted. + * @see java.awt.Image + * @see java.awt.image.ImageObserver + * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) + */ public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, - ImageObserver observer) { - throw new HSLFException("Not implemented"); + ImageObserver observer){ + log.warn("Not implemented"); + + return false; } + /** + * Draws as much of the specified area of the specified image as is + * currently available, scaling it on the fly to fit inside the + * specified area of the destination drawable surface. Transparent pixels + * do not affect whatever pixels are already there. + *

+ * This method returns immediately in all cases, even if the + * image area to be drawn has not yet been scaled, dithered, and converted + * for the current output device. + * If the current output representation is not yet complete then + * drawImage returns false. As more of + * the image becomes available, the process that draws the image notifies + * the specified image observer. + *

+ * This method always uses the unscaled version of the image + * to render the scaled rectangle and performs the required + * scaling on the fly. It does not use a cached, scaled version + * of the image for this operation. Scaling of the image from source + * to destination is performed such that the first coordinate + * of the source rectangle is mapped to the first coordinate of + * the destination rectangle, and the second source coordinate is + * mapped to the second destination coordinate. The subimage is + * scaled and flipped as needed to preserve those mappings. + * @param img the specified image to be drawn + * @param dx1 the x coordinate of the first corner of the + * destination rectangle. + * @param dy1 the y coordinate of the first corner of the + * destination rectangle. + * @param dx2 the x coordinate of the second corner of the + * destination rectangle. + * @param dy2 the y coordinate of the second corner of the + * destination rectangle. + * @param sx1 the x coordinate of the first corner of the + * source rectangle. + * @param sy1 the y coordinate of the first corner of the + * source rectangle. + * @param sx2 the x coordinate of the second corner of the + * source rectangle. + * @param sy2 the y coordinate of the second corner of the + * source rectangle. + * @param observer object to be notified as more of the image is + * scaled and converted. + * @see java.awt.Image + * @see java.awt.image.ImageObserver + * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) + * @since JDK1.1 + */ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, - ImageObserver observer) { - throw new HSLFException("Not implemented"); + ImageObserver observer){ + log.warn("Not implemented"); + return false; } + /** + * Draws as much of the specified area of the specified image as is + * currently available, scaling it on the fly to fit inside the + * specified area of the destination drawable surface. + *

+ * Transparent pixels are drawn in the specified background color. + * This operation is equivalent to filling a rectangle of the + * width and height of the specified image with the given color and then + * drawing the image on top of it, but possibly more efficient. + *

+ * This method returns immediately in all cases, even if the + * image area to be drawn has not yet been scaled, dithered, and converted + * for the current output device. + * If the current output representation is not yet complete then + * drawImage returns false. As more of + * the image becomes available, the process that draws the image notifies + * the specified image observer. + *

+ * This method always uses the unscaled version of the image + * to render the scaled rectangle and performs the required + * scaling on the fly. It does not use a cached, scaled version + * of the image for this operation. Scaling of the image from source + * to destination is performed such that the first coordinate + * of the source rectangle is mapped to the first coordinate of + * the destination rectangle, and the second source coordinate is + * mapped to the second destination coordinate. The subimage is + * scaled and flipped as needed to preserve those mappings. + * @param img the specified image to be drawn + * @param dx1 the x coordinate of the first corner of the + * destination rectangle. + * @param dy1 the y coordinate of the first corner of the + * destination rectangle. + * @param dx2 the x coordinate of the second corner of the + * destination rectangle. + * @param dy2 the y coordinate of the second corner of the + * destination rectangle. + * @param sx1 the x coordinate of the first corner of the + * source rectangle. + * @param sy1 the y coordinate of the first corner of the + * source rectangle. + * @param sx2 the x coordinate of the second corner of the + * source rectangle. + * @param sy2 the y coordinate of the second corner of the + * source rectangle. + * @param bgcolor the background color to paint under the + * non-opaque portions of the image. + * @param observer object to be notified as more of the image is + * scaled and converted. + * @see java.awt.Image + * @see java.awt.image.ImageObserver + * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) + * @since JDK1.1 + */ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, - ImageObserver observer) { - throw new HSLFException("Not implemented"); - } - - public boolean drawImage(Image img, int x, int y, - ImageObserver observer) { - throw new HSLFException("Not implemented"); + ImageObserver observer){ + log.warn("Not implemented"); + return false; } + /** + * Draws as much of the specified image as is currently available. + * The image is drawn with its top-left corner at + * (xy) in this graphics context's coordinate + * space. Transparent pixels in the image do not affect whatever + * pixels are already there. + *

+ * This method returns immediately in all cases, even if the + * complete image has not yet been loaded, and it has not been dithered + * and converted for the current output device. + *

+ * If the image has completely loaded and its pixels are + * no longer being changed, then + * drawImage returns true. + * Otherwise, drawImage returns false + * and as more of + * the image becomes available + * or it is time to draw another frame of animation, + * the process that loads the image notifies + * the specified image observer. + * @param img the specified image to be drawn. This method does + * nothing if img is null. + * @param x the x coordinate. + * @param y the y coordinate. + * @param observer object to be notified as more of + * the image is converted. + * @return false if the image pixels are still changing; + * true otherwise. + * @see java.awt.Image + * @see java.awt.image.ImageObserver + * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) + */ public boolean drawImage(Image img, int x, int y, - int width, int height, ImageObserver observer) { - throw new HSLFException("Not implemented"); + log.warn("Not implemented"); + return false; } + /** + * Disposes of this graphics context and releases + * any system resources that it is using. + * A Graphics object cannot be used after + * disposehas been called. + *

+ * When a Java program runs, a large number of Graphics + * objects can be created within a short time frame. + * Although the finalization process of the garbage collector + * also disposes of the same system resources, it is preferable + * to manually free the associated resources by calling this + * method rather than to rely on a finalization process which + * may not run to completion for a long period of time. + *

+ * Graphics objects which are provided as arguments to the + * paint and update methods + * of components are automatically released by the system when + * those methods return. For efficiency, programmers should + * call dispose when finished using + * a Graphics object only if it was created + * directly from a component or another Graphics object. + * @see java.awt.Graphics#finalize + * @see java.awt.Component#paint + * @see java.awt.Component#update + * @see java.awt.Component#getGraphics + * @see java.awt.Graphics#create + */ public void dispose() { - throw new HSLFException("Not implemented"); + ; } - public void drawLine(int x1, int y1, int x2, int y2) { - Line line = new Line(); - line.setAnchor(new java.awt.Rectangle(x1, y1, x2-x1, y2-y1)); - if (stroke instanceof BasicStroke){ - BasicStroke bs = (BasicStroke)stroke; - line.setLineWidth(bs.getLineWidth()); - } - if(getColor() != null) line.setLineColor(getColor()); - group.addShape(line); + /** + * Draws a line, using the current color, between the points + * (x1, y1) and (x2, y2) + * in this graphics context's coordinate system. + * @param x1 the first point's x coordinate. + * @param y1 the first point's y coordinate. + * @param x2 the second point's x coordinate. + * @param y2 the second point's y coordinate. + */ + public void drawLine(int x1, int y1, int x2, int y2){ + Line2D line = new Line2D.Float(x1, y1, x2, y2); + draw(line); } - public void fillPolygon(int xPoints[], int yPoints[], - int nPoints) { - throw new HSLFException("Not implemented"); + /** + * Fills a closed polygon defined by + * arrays of x and y coordinates. + *

+ * This method draws the polygon defined by nPoint line + * segments, where the first nPoint - 1 + * line segments are line segments from + * (xPoints[i - 1], yPoints[i - 1]) + * to (xPoints[i], yPoints[i]), for + * 1 ≤ i ≤ nPoints. + * The figure is automatically closed by drawing a line connecting + * the final point to the first point, if those points are different. + *

+ * The area inside the polygon is defined using an + * even-odd fill rule, also known as the alternating rule. + * @param xPoints a an array of x coordinates. + * @param yPoints a an array of y coordinates. + * @param nPoints a the total number of points. + * @see java.awt.Graphics#drawPolygon(int[], int[], int) + */ + public void fillPolygon(int[] xPoints, int[] yPoints, + int nPoints){ + java.awt.Polygon polygon = new java.awt.Polygon(xPoints, yPoints, nPoints); + fill(polygon); } - public FontMetrics getFontMetrics(Font f) { - throw new HSLFException("Not implemented"); + /** + * Fills the specified rectangle. + * The left and right edges of the rectangle are at + * x and x + width - 1. + * The top and bottom edges are at + * y and y + height - 1. + * The resulting rectangle covers an area + * width pixels wide by + * height pixels tall. + * The rectangle is filled using the graphics context's current color. + * @param x the x coordinate + * of the rectangle to be filled. + * @param y the y coordinate + * of the rectangle to be filled. + * @param width the width of the rectangle to be filled. + * @param height the height of the rectangle to be filled. + * @see java.awt.Graphics#clearRect + * @see java.awt.Graphics#drawRect + */ + public void fillRect(int x, int y, int width, int height){ + AutoShape shape = new AutoShape(ShapeTypes.Rectangle, group); + applyPaint(shape); + applyStroke(shape); + shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); + group.addShape(shape); } - public void fillRect(int x, int y, int width, int height) { - throw new HSLFException("Not implemented"); + /** + * Draws the outline of the specified rectangle. + * The left and right edges of the rectangle are at + * x and x + width. + * The top and bottom edges are at + * y and y + height. + * The rectangle is drawn using the graphics context's current color. + * @param x the x coordinate + * of the rectangle to be drawn. + * @param y the y coordinate + * of the rectangle to be drawn. + * @param width the width of the rectangle to be drawn. + * @param height the height of the rectangle to be drawn. + * @see java.awt.Graphics#fillRect + * @see java.awt.Graphics#clearRect + */ + public void drawRect(int x, int y, int width, int height) { + AutoShape shape = new AutoShape(ShapeTypes.Rectangle, group); + shape.setFillColor(null); + applyStroke(shape); + shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); + group.addShape(shape); + } - public void drawPolygon(int xPoints[], int yPoints[], - int nPoints) { - throw new HSLFException("Not implemented"); + /** + * Draws a closed polygon defined by + * arrays of x and y coordinates. + * Each pair of (xy) coordinates defines a point. + *

+ * This method draws the polygon defined by nPoint line + * segments, where the first nPoint - 1 + * line segments are line segments from + * (xPoints[i - 1], yPoints[i - 1]) + * to (xPoints[i], yPoints[i]), for + * 1 ≤ i ≤ nPoints. + * The figure is automatically closed by drawing a line connecting + * the final point to the first point, if those points are different. + * @param xPoints a an array of x coordinates. + * @param yPoints a an array of y coordinates. + * @param nPoints a the total number of points. + * @see java.awt.Graphics#fillPolygon(int[],int[],int) + * @see java.awt.Graphics#drawPolyline + */ + public void drawPolygon(int[] xPoints, int[] yPoints, + int nPoints){ + java.awt.Polygon polygon = new java.awt.Polygon(xPoints, yPoints, nPoints); + draw(polygon); } - public void clipRect(int x, int y, int width, int height) { - throw new HSLFException("Not implemented"); + /** + * Intersects the current clip with the specified rectangle. + * The resulting clipping area is the intersection of the current + * clipping area and the specified rectangle. If there is no + * current clipping area, either because the clip has never been + * set, or the clip has been cleared using setClip(null), + * the specified rectangle becomes the new clip. + * This method sets the user clip, which is independent of the + * clipping associated with device bounds and window visibility. + * This method can only be used to make the current clip smaller. + * To set the current clip larger, use any of the setClip methods. + * Rendering operations have no effect outside of the clipping area. + * @param x the x coordinate of the rectangle to intersect the clip with + * @param y the y coordinate of the rectangle to intersect the clip with + * @param width the width of the rectangle to intersect the clip with + * @param height the height of the rectangle to intersect the clip with + * @see #setClip(int, int, int, int) + * @see #setClip(Shape) + */ + public void clipRect(int x, int y, int width, int height){ + clip(new Rectangle(x, y, width, height)); } + /** + * Sets the current clipping area to an arbitrary clip shape. + * Not all objects that implement the Shape + * interface can be used to set the clip. The only + * Shape objects that are guaranteed to be + * supported are Shape objects that are + * obtained via the getClip method and via + * Rectangle objects. This method sets the + * user clip, which is independent of the clipping associated + * with device bounds and window visibility. + * @param clip the Shape to use to set the clip + * @see java.awt.Graphics#getClip() + * @see java.awt.Graphics#clipRect + * @see java.awt.Graphics#setClip(int, int, int, int) + * @since JDK1.1 + */ public void setClip(Shape clip) { - throw new HSLFException("Not implemented"); + log.warn("Not implemented"); } - public java.awt.Rectangle getClipBounds() { - throw new HSLFException("Not implemented"); + /** + * Returns the bounding rectangle of the current clipping area. + * This method refers to the user clip, which is independent of the + * clipping associated with device bounds and window visibility. + * If no clip has previously been set, or if the clip has been + * cleared using setClip(null), this method returns + * null. + * The coordinates in the rectangle are relative to the coordinate + * system origin of this graphics context. + * @return the bounding rectangle of the current clipping area, + * or null if no clip is set. + * @see java.awt.Graphics#getClip + * @see java.awt.Graphics#clipRect + * @see java.awt.Graphics#setClip(int, int, int, int) + * @see java.awt.Graphics#setClip(Shape) + * @since JDK1.1 + */ + public Rectangle getClipBounds(){ + Shape c = getClip(); + if(c==null) + return null; + else + return c.getBounds(); } - public void drawString(AttributedCharacterIterator iterator, int x, int y) { - throw new HSLFException("Not implemented"); + /** + * Draws the text given by the specified iterator, using this + * graphics context's current color. The iterator has to specify a font + * for each character. The baseline of the + * first character is at position (xy) in this + * graphics context's coordinate system. + * @param iterator the iterator whose text is to be drawn + * @param x the x coordinate. + * @param y the y coordinate. + * @see java.awt.Graphics#drawBytes + * @see java.awt.Graphics#drawChars + */ + public void drawString(AttributedCharacterIterator iterator, + int x, int y){ + drawString(iterator, (float)x, (float)y); } + /** + * Clears the specified rectangle by filling it with the background + * color of the current drawing surface. This operation does not + * use the current paint mode. + *

+ * Beginning with Java 1.1, the background color + * of offscreen images may be system dependent. Applications should + * use setColor followed by fillRect to + * ensure that an offscreen image is cleared to a specific color. + * @param x the x coordinate of the rectangle to clear. + * @param y the y coordinate of the rectangle to clear. + * @param width the width of the rectangle to clear. + * @param height the height of the rectangle to clear. + * @see java.awt.Graphics#fillRect(int, int, int, int) + * @see java.awt.Graphics#drawRect + * @see java.awt.Graphics#setColor(java.awt.Color) + * @see java.awt.Graphics#setPaintMode + * @see java.awt.Graphics#setXORMode(java.awt.Color) + */ public void clearRect(int x, int y, int width, int height) { - throw new HSLFException("Not implemented"); + Paint paint = getPaint(); + setColor(getBackground()); + fillRect(x, y, width, height); + setPaint(paint); } public void copyArea(int x, int y, int width, int height, int dx, int dy) { - throw new HSLFException("Not implemented"); + ; } - public void setClip(int x, int y, int width, int height) { - throw new HSLFException("Not implemented"); + /** + * Sets the current clip to the rectangle specified by the given + * coordinates. This method sets the user clip, which is + * independent of the clipping associated with device bounds + * and window visibility. + * Rendering operations have no effect outside of the clipping area. + * @param x the x coordinate of the new clip rectangle. + * @param y the y coordinate of the new clip rectangle. + * @param width the width of the new clip rectangle. + * @param height the height of the new clip rectangle. + * @see java.awt.Graphics#clipRect + * @see java.awt.Graphics#setClip(Shape) + * @since JDK1.1 + */ + public void setClip(int x, int y, int width, int height){ + setClip(new Rectangle(x, y, width, height)); } - public void rotate(double d) { - throw new HSLFException("Not implemented"); - + /** + * Concatenates the current Graphics2D + * Transform with a rotation transform. + * Subsequent rendering is rotated by the specified radians relative + * to the previous origin. + * This is equivalent to calling transform(R), where R is an + * AffineTransform represented by the following matrix: + *

+     *          [   cos(theta)    -sin(theta)    0   ]
+     *          [   sin(theta)     cos(theta)    0   ]
+     *          [       0              0         1   ]
+     * 
+ * Rotating with a positive angle theta rotates points on the positive + * x axis toward the positive y axis. + * @param theta the angle of rotation in radians + */ + public void rotate(double theta){ + transform.rotate(theta); } - public void rotate(double d, double d1, double d2) { - throw new HSLFException("Not implemented"); + /** + * Concatenates the current Graphics2D + * Transform with a translated rotation + * transform. Subsequent rendering is transformed by a transform + * which is constructed by translating to the specified location, + * rotating by the specified radians, and translating back by the same + * amount as the original translation. This is equivalent to the + * following sequence of calls: + *
+     *          translate(x, y);
+     *          rotate(theta);
+     *          translate(-x, -y);
+     * 
+ * Rotating with a positive angle theta rotates points on the positive + * x axis toward the positive y axis. + * @param theta the angle of rotation in radians + * @param x x coordinate of the origin of the rotation + * @param y y coordinate of the origin of the rotation + */ + public void rotate(double theta, double x, double y){ + transform.rotate(theta, x, y); } - public void shear(double d, double d1) { - throw new HSLFException("Not implemented"); + /** + * Concatenates the current Graphics2D + * Transform with a shearing transform. + * Subsequent renderings are sheared by the specified + * multiplier relative to the previous position. + * This is equivalent to calling transform(SH), where SH + * is an AffineTransform represented by the following + * matrix: + *
+     *          [   1   shx   0   ]
+     *          [  shy   1    0   ]
+     *          [   0    0    1   ]
+     * 
+ * @param shx the multiplier by which coordinates are shifted in + * the positive X axis direction as a function of their Y coordinate + * @param shy the multiplier by which coordinates are shifted in + * the positive Y axis direction as a function of their X coordinate + */ + public void shear(double shx, double shy){ + transform.shear(shx, shy); } + /** + * Get the rendering context of the Font within this + * Graphics2D context. + * The {@link FontRenderContext} + * encapsulates application hints such as anti-aliasing and + * fractional metrics, as well as target device specific information + * such as dots-per-inch. This information should be provided by the + * application when using objects that perform typographical + * formatting, such as Font and + * TextLayout. This information should also be provided + * by applications that perform their own layout and need accurate + * measurements of various characteristics of glyphs such as advance + * and line height when various rendering hints have been applied to + * the text rendering. + * + * @return a reference to an instance of FontRenderContext. + * @see java.awt.font.FontRenderContext + * @see java.awt.Font#createGlyphVector(FontRenderContext,char[]) + * @see java.awt.font.TextLayout + * @since JDK1.2 + */ public FontRenderContext getFontRenderContext() { - return new FontRenderContext(transform, true, true); + boolean isAntiAliased = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals( + getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); + boolean usesFractionalMetrics = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals( + getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS)); + + + return new FontRenderContext(new AffineTransform(), isAntiAliased, usesFractionalMetrics); } - public void transform(AffineTransform affinetransform) { - throw new HSLFException("Not implemented"); + /** + * Composes an AffineTransform object with the + * Transform in this Graphics2D according + * to the rule last-specified-first-applied. If the current + * Transform is Cx, the result of composition + * with Tx is a new Transform Cx'. Cx' becomes the + * current Transform for this Graphics2D. + * Transforming a point p by the updated Transform Cx' is + * equivalent to first transforming p by Tx and then transforming + * the result by the original Transform Cx. In other + * words, Cx'(p) = Cx(Tx(p)). A copy of the Tx is made, if necessary, + * so further modifications to Tx do not affect rendering. + * @param Tx the AffineTransform object to be composed with + * the current Transform + * @see #setTransform + * @see AffineTransform + */ + public void transform(AffineTransform Tx) { + transform.concatenate(Tx); } - public void drawImage(BufferedImage bufferedimage, BufferedImageOp op, int x, int y) { - throw new HSLFException("Not implemented"); + /** + * Renders a BufferedImage that is + * filtered with a + * {@link BufferedImageOp}. + * The rendering attributes applied include the Clip, + * Transform + * and Composite attributes. This is equivalent to: + *
+     * img1 = op.filter(img, null);
+     * drawImage(img1, new AffineTransform(1f,0f,0f,1f,x,y), null);
+     * 
+ * @param img the BufferedImage to be rendered + * @param op the filter to be applied to the image before rendering + * @param x the x coordinate in user space where the image is rendered + * @param y the y coordinate in user space where the image is rendered + * @see #transform + * @see #setTransform + * @see #setComposite + * @see #clip + * @see #setClip(Shape) + */ + public void drawImage(BufferedImage img, + BufferedImageOp op, + int x, + int y){ + img = op.filter(img, null); + drawImage(img, x, y, null); } - public void setBackground(Color c) { - throw new HSLFException("Not implemented"); + /** + * Sets the background color for the Graphics2D context. + * The background color is used for clearing a region. + * When a Graphics2D is constructed for a + * Component, the background color is + * inherited from the Component. Setting the background color + * in the Graphics2D context only affects the subsequent + * clearRect calls and not the background color of the + * Component. To change the background + * of the Component, use appropriate methods of + * the Component. + * @param color the background color that isused in + * subsequent calls to clearRect + * @see #getBackground + * @see java.awt.Graphics#clearRect + */ + public void setBackground(Color color) { + if(color == null) + return; + + background = color; } - public void drawRenderedImage(RenderedImage renderedimage, AffineTransform affinetransform) { - throw new HSLFException("Not implemented"); + /** + * Returns the background color used for clearing a region. + * @return the current Graphics2D Color, + * which defines the background color. + * @see #setBackground + */ + public Color getBackground(){ + return background; } - public Color getBackground() { - throw new HSLFException("Not implemented"); + /** + * Sets the Composite for the Graphics2D context. + * The Composite is used in all drawing methods such as + * drawImage, drawString, draw, + * and fill. It specifies how new pixels are to be combined + * with the existing pixels on the graphics device during the rendering + * process. + *

If this Graphics2D context is drawing to a + * Component on the display screen and the + * Composite is a custom object rather than an + * instance of the AlphaComposite class, and if + * there is a security manager, its checkPermission + * method is called with an AWTPermission("readDisplayPixels") + * permission. + * + * @param comp the Composite object to be used for rendering + * @throws SecurityException + * if a custom Composite object is being + * used to render to the screen and a security manager + * is set and its checkPermission method + * does not allow the operation. + * @see java.awt.Graphics#setXORMode + * @see java.awt.Graphics#setPaintMode + * @see java.awt.AlphaComposite + */ + public void setComposite(Composite comp){ + log.warn("Not implemented"); } - public void setComposite(Composite composite) { - throw new HSLFException("Not implemented"); + /** + * Returns the current Composite in the + * Graphics2D context. + * @return the current Graphics2D Composite, + * which defines a compositing style. + * @see #setComposite + */ + public Composite getComposite(){ + log.warn("Not implemented"); + return null; + } + /** + * Returns the value of a single preference for the rendering algorithms. + * Hint categories include controls for rendering quality and overall + * time/quality trade-off in the rendering process. Refer to the + * RenderingHints class for definitions of some common + * keys and values. + * @param hintKey the key corresponding to the hint to get. + * @return an object representing the value for the specified hint key. + * Some of the keys and their associated values are defined in the + * RenderingHints class. + * @see RenderingHints + */ + public Object getRenderingHint(RenderingHints.Key hintKey){ + return hints.get(hintKey); } - public Composite getComposite() { - throw new HSLFException("Not implemented"); + /** + * Sets the value of a single preference for the rendering algorithms. + * Hint categories include controls for rendering quality and overall + * time/quality trade-off in the rendering process. Refer to the + * RenderingHints class for definitions of some common + * keys and values. + * @param hintKey the key of the hint to be set. + * @param hintValue the value indicating preferences for the specified + * hint category. + * @see RenderingHints + */ + public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue){ + hints.put(hintKey, hintValue); } - public Object getRenderingHint(java.awt.RenderingHints.Key key) { - throw new HSLFException("Not implemented"); + + /** + * Renders the text of the specified + * {@link GlyphVector} using + * the Graphics2D context's rendering attributes. + * The rendering attributes applied include the Clip, + * Transform, Paint, and + * Composite attributes. The GlyphVector + * specifies individual glyphs from a {@link Font}. + * The GlyphVector can also contain the glyph positions. + * This is the fastest way to render a set of characters to the + * screen. + * + * @param g the GlyphVector to be rendered + * @param x the x position in user space where the glyphs should be + * rendered + * @param y the y position in user space where the glyphs should be + * rendered + * + * @see java.awt.Font#createGlyphVector(FontRenderContext, char[]) + * @see java.awt.font.GlyphVector + * @see #setPaint + * @see java.awt.Graphics#setColor + * @see #setTransform + * @see #setComposite + * @see #setClip(Shape) + */ + public void drawGlyphVector(GlyphVector g, float x, float y) { + Shape glyphOutline = g.getOutline(x, y); + fill(glyphOutline); } - public boolean drawImage(Image image, AffineTransform affinetransform, ImageObserver imageobserver) { - throw new HSLFException("Not implemented"); + /** + * Returns the device configuration associated with this + * Graphics2D. + * @return the device configuration + */ + public GraphicsConfiguration getDeviceConfiguration() { + return GraphicsEnvironment.getLocalGraphicsEnvironment(). + getDefaultScreenDevice().getDefaultConfiguration(); } - public void setRenderingHint(java.awt.RenderingHints.Key key, Object obj) { - throw new HSLFException("Not implemented"); + /** + * Sets the values of an arbitrary number of preferences for the + * rendering algorithms. + * Only values for the rendering hints that are present in the + * specified Map object are modified. + * All other preferences not present in the specified + * object are left unmodified. + * Hint categories include controls for rendering quality and + * overall time/quality trade-off in the rendering process. + * Refer to the RenderingHints class for definitions of + * some common keys and values. + * @param hints the rendering hints to be set + * @see RenderingHints + */ + public void addRenderingHints(Map hints){ + this.hints.putAll(hints); } + /** + * Concatenates the current + * Graphics2D Transform + * with a translation transform. + * Subsequent rendering is translated by the specified + * distance relative to the previous position. + * This is equivalent to calling transform(T), where T is an + * AffineTransform represented by the following matrix: + *

+     *          [   1    0    tx  ]
+     *          [   0    1    ty  ]
+     *          [   0    0    1   ]
+     * 
+ * @param tx the distance to translate along the x-axis + * @param ty the distance to translate along the y-axis + */ + public void translate(double tx, double ty){ + transform.translate(tx, ty); + } - public void drawGlyphVector(GlyphVector g, float x, float y) { - throw new HSLFException("Not implemented"); + /** + * Renders the text of the specified iterator, using the + * Graphics2D context's current Paint. The + * iterator must specify a font + * for each character. The baseline of the + * first character is at position (xy) in the + * User Space. + * The rendering attributes applied include the Clip, + * Transform, Paint, and + * Composite attributes. + * For characters in script systems such as Hebrew and Arabic, + * the glyphs can be rendered from right to left, in which case the + * coordinate supplied is the location of the leftmost character + * on the baseline. + * @param iterator the iterator whose text is to be rendered + * @param x the x coordinate where the iterator's text is to be + * rendered + * @param y the y coordinate where the iterator's text is to be + * rendered + * @see #setPaint + * @see java.awt.Graphics#setColor + * @see #setTransform + * @see #setComposite + * @see #setClip + */ + public void drawString(AttributedCharacterIterator iterator, float x, float y) { + log.warn("Not implemented"); + } + /** + * Checks whether or not the specified Shape intersects + * the specified {@link Rectangle}, which is in device + * space. If onStroke is false, this method checks + * whether or not the interior of the specified Shape + * intersects the specified Rectangle. If + * onStroke is true, this method checks + * whether or not the Stroke of the specified + * Shape outline intersects the specified + * Rectangle. + * The rendering attributes taken into account include the + * Clip, Transform, and Stroke + * attributes. + * @param rect the area in device space to check for a hit + * @param s the Shape to check for a hit + * @param onStroke flag used to choose between testing the + * stroked or the filled shape. If the flag is true, the + * Stroke oultine is tested. If the flag is + * false, the filled Shape is tested. + * @return true if there is a hit; false + * otherwise. + * @see #setStroke + * @see #fill(Shape) + * @see #draw(Shape) + * @see #transform + * @see #setTransform + * @see #clip + * @see #setClip(Shape) + */ + public boolean hit(Rectangle rect, + Shape s, + boolean onStroke){ + if (onStroke) { + s = getStroke().createStrokedShape(s); + } + + s = getTransform().createTransformedShape(s); + + return s.intersects(rect); } - public GraphicsConfiguration getDeviceConfiguration() { - throw new HSLFException("Not implemented"); + /** + * Gets the preferences for the rendering algorithms. Hint categories + * include controls for rendering quality and overall time/quality + * trade-off in the rendering process. + * Returns all of the hint key/value pairs that were ever specified in + * one operation. Refer to the + * RenderingHints class for definitions of some common + * keys and values. + * @return a reference to an instance of RenderingHints + * that contains the current preferences. + * @see RenderingHints + */ + public RenderingHints getRenderingHints(){ + return hints; + } + + /** + * Replaces the values of all preferences for the rendering + * algorithms with the specified hints. + * The existing values for all rendering hints are discarded and + * the new set of known hints and values are initialized from the + * specified {@link Map} object. + * Hint categories include controls for rendering quality and + * overall time/quality trade-off in the rendering process. + * Refer to the RenderingHints class for definitions of + * some common keys and values. + * @param hints the rendering hints to be set + * @see RenderingHints + */ + public void setRenderingHints(Map hints){ + this.hints = new RenderingHints(hints); + } + + /** + * Renders an image, applying a transform from image space into user space + * before drawing. + * The transformation from user space into device space is done with + * the current Transform in the Graphics2D. + * The specified transformation is applied to the image before the + * transform attribute in the Graphics2D context is applied. + * The rendering attributes applied include the Clip, + * Transform, and Composite attributes. + * Note that no rendering is done if the specified transform is + * noninvertible. + * @param img the Image to be rendered + * @param xform the transformation from image space into user space + * @param obs the {@link ImageObserver} + * to be notified as more of the Image + * is converted + * @return true if the Image is + * fully loaded and completely rendered; + * false if the Image is still being loaded. + * @see #transform + * @see #setTransform + * @see #setComposite + * @see #clip + * @see #setClip(Shape) + */ + public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { + log.warn("Not implemented"); + return false; } - public void addRenderingHints(Map map) { - throw new HSLFException("Not implemented"); + /** + * Draws as much of the specified image as has already been scaled + * to fit inside the specified rectangle. + *

+ * The image is drawn inside the specified rectangle of this + * graphics context's coordinate space, and is scaled if + * necessary. Transparent pixels do not affect whatever pixels + * are already there. + *

+ * This method returns immediately in all cases, even if the + * entire image has not yet been scaled, dithered, and converted + * for the current output device. + * If the current output representation is not yet complete, then + * drawImage returns false. As more of + * the image becomes available, the process that loads the image notifies + * the image observer by calling its imageUpdate method. + *

+ * A scaled version of an image will not necessarily be + * available immediately just because an unscaled version of the + * image has been constructed for this output device. Each size of + * the image may be cached separately and generated from the original + * data in a separate image production sequence. + * @param img the specified image to be drawn. This method does + * nothing if img is null. + * @param x the x coordinate. + * @param y the y coordinate. + * @param width the width of the rectangle. + * @param height the height of the rectangle. + * @param observer object to be notified as more of + * the image is converted. + * @return false if the image pixels are still changing; + * true otherwise. + * @see java.awt.Image + * @see java.awt.image.ImageObserver + * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) + */ + public boolean drawImage(Image img, int x, int y, + int width, int height, + ImageObserver observer) { + log.warn("Not implemented"); + return false; } - public void translate(double d, double d1) { + /** + * Creates a new Graphics object that is + * a copy of this Graphics object. + * @return a new graphics context that is a copy of + * this graphics context. + */ + public Graphics create() { + try { + return (Graphics)clone(); + } catch (CloneNotSupportedException e){ + throw new HSLFException(e); + } + } - throw new HSLFException("Not implemented"); + /** + * Gets the font metrics for the specified font. + * @return the font metrics for the specified font. + * @param f the specified font + * @see java.awt.Graphics#getFont + * @see java.awt.FontMetrics + * @see java.awt.Graphics#getFontMetrics() + */ + public FontMetrics getFontMetrics(Font f) { + return Toolkit.getDefaultToolkit().getFontMetrics(f); } - public void drawString(AttributedCharacterIterator attributedcharacteriterator, float x, float y) { - throw new HSLFException("Not implemented"); + /** + * Sets the paint mode of this graphics context to alternate between + * this graphics context's current color and the new specified color. + * This specifies that logical pixel operations are performed in the + * XOR mode, which alternates pixels between the current color and + * a specified XOR color. + *

+ * When drawing operations are performed, pixels which are the + * current color are changed to the specified color, and vice versa. + *

+ * Pixels that are of colors other than those two colors are changed + * in an unpredictable but reversible manner; if the same figure is + * drawn twice, then all pixels are restored to their original values. + * @param c1 the XOR alternation color + */ + public void setXORMode(Color c1) { + log.warn("Not implemented"); } - public boolean hit(java.awt.Rectangle rectangle, Shape shape, boolean flag) { - throw new HSLFException("Not implemented"); + /** + * Sets the paint mode of this graphics context to overwrite the + * destination with this graphics context's current color. + * This sets the logical pixel operation function to the paint or + * overwrite mode. All subsequent rendering operations will + * overwrite the destination with the current color. + */ + public void setPaintMode() { + log.warn("Not implemented"); } - public RenderingHints getRenderingHints() { - throw new HSLFException("Not implemented"); + /** + * Renders a + * {@link RenderableImage}, + * applying a transform from image space into user space before drawing. + * The transformation from user space into device space is done with + * the current Transform in the Graphics2D. + * The specified transformation is applied to the image before the + * transform attribute in the Graphics2D context is applied. + * The rendering attributes applied include the Clip, + * Transform, and Composite attributes. Note + * that no rendering is done if the specified transform is + * noninvertible. + *

+ * Rendering hints set on the Graphics2D object might + * be used in rendering the RenderableImage. + * If explicit control is required over specific hints recognized by a + * specific RenderableImage, or if knowledge of which hints + * are used is required, then a RenderedImage should be + * obtained directly from the RenderableImage + * and rendered using + *{@link #drawRenderedImage(RenderedImage, AffineTransform) drawRenderedImage}. + * @param img the image to be rendered. This method does + * nothing if img is null. + * @param xform the transformation from image space into user space + * @see #transform + * @see #setTransform + * @see #setComposite + * @see #clip + * @see #setClip + * @see #drawRenderedImage + */ + public void drawRenderedImage(RenderedImage img, AffineTransform xform) { + log.warn("Not implemented"); } - public void setRenderingHints(Map map) { - throw new HSLFException("Not implemented"); + /** + * Renders a {@link RenderedImage}, + * applying a transform from image + * space into user space before drawing. + * The transformation from user space into device space is done with + * the current Transform in the Graphics2D. + * The specified transformation is applied to the image before the + * transform attribute in the Graphics2D context is applied. + * The rendering attributes applied include the Clip, + * Transform, and Composite attributes. Note + * that no rendering is done if the specified transform is + * noninvertible. + * @param img the image to be rendered. This method does + * nothing if img is null. + * @param xform the transformation from image space into user space + * @see #transform + * @see #setTransform + * @see #setComposite + * @see #clip + * @see #setClip + */ + public void drawRenderableImage(RenderableImage img, AffineTransform xform) { + log.warn("Not implemented"); + } + protected void applyStroke(SimpleShape shape) { + if (stroke instanceof BasicStroke){ + BasicStroke bs = (BasicStroke)stroke; + shape.setLineWidth(bs.getLineWidth()); + float[] dash = bs.getDashArray(); + if (dash != null) { + //TODO: implement more dashing styles + shape.setLineDashing(Line.PEN_DASH); + } + } } - public void drawRenderableImage(RenderableImage renderableimage, AffineTransform affinetransform) { - throw new HSLFException("Not implemented"); + protected void applyPaint(SimpleShape shape) { + if (paint instanceof Color) { + shape.getFill().setForegroundColor((Color)paint); + } } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Polygon.java b/src/scratchpad/src/org/apache/poi/hslf/model/Polygon.java new file mode 100755 index 0000000000..ea4afe01b2 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Polygon.java @@ -0,0 +1,160 @@ +/* ==================================================================== + 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.hslf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.util.LittleEndian; + +import java.awt.geom.Point2D; + +/** + * A simple closed polygon shape + * + * @author Yegor Kozlov + */ +public class Polygon extends AutoShape { + /** + * Create a Polygon object and initialize it from the supplied Record container. + * + * @param escherRecord EscherSpContainer container which holds information about this shape + * @param parent the parent of the shape + */ + protected Polygon(EscherContainerRecord escherRecord, Shape parent){ + super(escherRecord, parent); + + } + + /** + * Create a new Polygon. This constructor is used when a new shape is created. + * + * @param parent the parent of this Shape. For example, if this text box is a cell + * in a table then the parent is Table. + */ + public Polygon(Shape parent){ + super(null, parent); + _escherContainer = createSpContainer(ShapeTypes.NotPrimitive, parent instanceof ShapeGroup); + } + + /** + * Create a new Polygon. This constructor is used when a new shape is created. + * + */ + public Polygon(){ + this(null); + } + + /** + * Set the polygon vertices + * + * @param xPoints + * @param yPoints + */ + public void setPoints(float[] xPoints, float[] yPoints) + { + float right = findBiggest(xPoints); + float bottom = findBiggest(yPoints); + float left = findSmallest(xPoints); + float top = findSmallest(yPoints); + + EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__RIGHT, (int)((right - left)*POINT_DPI/MASTER_DPI))); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__BOTTOM, (int)((bottom - top)*POINT_DPI/MASTER_DPI))); + + for (int i = 0; i < xPoints.length; i++) { + xPoints[i] += -left; + yPoints[i] += -top; + } + + int numpoints = xPoints.length; + + EscherArrayProperty verticesProp = new EscherArrayProperty(EscherProperties.GEOMETRY__VERTICES, false, new byte[0] ); + verticesProp.setNumberOfElementsInArray(numpoints+1); + verticesProp.setNumberOfElementsInMemory(numpoints+1); + verticesProp.setSizeOfElements(0xFFF0); + for (int i = 0; i < numpoints; i++) + { + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)(xPoints[i]*POINT_DPI/MASTER_DPI)); + LittleEndian.putShort(data, 2, (short)(yPoints[i]*POINT_DPI/MASTER_DPI)); + verticesProp.setElement(i, data); + } + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)(xPoints[0]*POINT_DPI/MASTER_DPI)); + LittleEndian.putShort(data, 2, (short)(yPoints[0]*POINT_DPI/MASTER_DPI)); + verticesProp.setElement(numpoints, data); + opt.addEscherProperty(verticesProp); + + EscherArrayProperty segmentsProp = new EscherArrayProperty(EscherProperties.GEOMETRY__SEGMENTINFO, false, null ); + segmentsProp.setSizeOfElements(0x0002); + segmentsProp.setNumberOfElementsInArray(numpoints * 2 + 4); + segmentsProp.setNumberOfElementsInMemory(numpoints * 2 + 4); + segmentsProp.setElement(0, new byte[] { (byte)0x00, (byte)0x40 } ); + segmentsProp.setElement(1, new byte[] { (byte)0x00, (byte)0xAC } ); + for (int i = 0; i < numpoints; i++) + { + segmentsProp.setElement(2 + i * 2, new byte[] { (byte)0x01, (byte)0x00 } ); + segmentsProp.setElement(3 + i * 2, new byte[] { (byte)0x00, (byte)0xAC } ); + } + segmentsProp.setElement(segmentsProp.getNumberOfElementsInArray() - 2, new byte[] { (byte)0x01, (byte)0x60 } ); + segmentsProp.setElement(segmentsProp.getNumberOfElementsInArray() - 1, new byte[] { (byte)0x00, (byte)0x80 } ); + opt.addEscherProperty(segmentsProp); + + opt.sortProperties(); + } + + /** + * Set the polygon vertices + * + * @param points the polygon vertices + */ + public void setPoints(Point2D[] points) + { + float[] xpoints = new float[points.length]; + float[] ypoints = new float[points.length]; + for (int i = 0; i < points.length; i++) { + xpoints[i] = (float)points[i].getX(); + ypoints[i] = (float)points[i].getY(); + + } + + setPoints(xpoints, ypoints); + } + + private float findBiggest( float[] values ) + { + float result = Float.MIN_VALUE; + for ( int i = 0; i < values.length; i++ ) + { + if (values[i] > result) + result = values[i]; + } + return result; + } + + private float findSmallest( float[] values ) + { + float result = Float.MAX_VALUE; + for ( int i = 0; i < values.length; i++ ) + { + if (values[i] < result) + result = values[i]; + } + return result; + } + + +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java index 5cff81a8d7..2e23c5926e 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java @@ -24,6 +24,7 @@ import org.apache.poi.util.POILogFactory; import java.util.Iterator; import java.awt.*; +import java.awt.geom.Rectangle2D; /** *

@@ -143,24 +144,39 @@ public abstract class Shape { * @return the anchor of this shape */ public java.awt.Rectangle getAnchor(){ + Rectangle2D anchor2d = getAnchor2D(); + return anchor2d.getBounds(); + } + + /** + * Returns the anchor (the bounding box rectangle) of this shape. + * All coordinates are expressed in points (72 dpi). + * + * @return the anchor of this shape + */ + public Rectangle2D getAnchor2D(){ EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); int flags = spRecord.getFlags(); - java.awt.Rectangle anchor=null; + Rectangle2D anchor=null; if ((flags & EscherSpRecord.FLAG_CHILD) != 0){ EscherChildAnchorRecord rec = (EscherChildAnchorRecord)getEscherChild(_escherContainer, EscherChildAnchorRecord.RECORD_ID); anchor = new java.awt.Rectangle(); - anchor.x = rec.getDx1()*POINT_DPI/MASTER_DPI; - anchor.y = rec.getDy1()*POINT_DPI/MASTER_DPI; - anchor.width = rec.getDx2()*POINT_DPI/MASTER_DPI - anchor.x; - anchor.height = rec.getDy2()*POINT_DPI/MASTER_DPI - anchor.y; + anchor = new Rectangle2D.Float( + (float)rec.getDx1()*POINT_DPI/MASTER_DPI, + (float)rec.getDy1()*POINT_DPI/MASTER_DPI, + (float)(rec.getDx2()-rec.getDx1())*POINT_DPI/MASTER_DPI, + (float)(rec.getDy2()-rec.getDy1())*POINT_DPI/MASTER_DPI + ); } else { EscherClientAnchorRecord rec = (EscherClientAnchorRecord)getEscherChild(_escherContainer, EscherClientAnchorRecord.RECORD_ID); anchor = new java.awt.Rectangle(); - anchor.y = rec.getFlag()*POINT_DPI/MASTER_DPI; - anchor.x = rec.getCol1()*POINT_DPI/MASTER_DPI; - anchor.width = (rec.getDx1() - rec.getCol1())*POINT_DPI/MASTER_DPI; - anchor.height = (rec.getRow1() - rec.getFlag())*POINT_DPI/MASTER_DPI; + anchor = new Rectangle2D.Float( + (float)rec.getCol1()*POINT_DPI/MASTER_DPI, + (float)rec.getFlag()*POINT_DPI/MASTER_DPI, + (float)(rec.getDx1()-rec.getCol1())*POINT_DPI/MASTER_DPI, + (float)(rec.getRow1()-rec.getFlag())*POINT_DPI/MASTER_DPI + ); } return anchor; } @@ -171,22 +187,22 @@ public abstract class Shape { * * @param anchor new anchor */ - public void setAnchor(java.awt.Rectangle anchor){ + public void setAnchor(Rectangle2D anchor){ EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); int flags = spRecord.getFlags(); if ((flags & EscherSpRecord.FLAG_CHILD) != 0){ EscherChildAnchorRecord rec = (EscherChildAnchorRecord)getEscherChild(_escherContainer, EscherChildAnchorRecord.RECORD_ID); - rec.setDx1(anchor.x*MASTER_DPI/POINT_DPI); - rec.setDy1(anchor.y*MASTER_DPI/POINT_DPI); - rec.setDx2((anchor.width + anchor.x)*MASTER_DPI/POINT_DPI); - rec.setDy2((anchor.height + anchor.y)*MASTER_DPI/POINT_DPI); + rec.setDx1((int)(anchor.getX()*MASTER_DPI/POINT_DPI)); + rec.setDy1((int)(anchor.getY()*MASTER_DPI/POINT_DPI)); + rec.setDx2((int)((anchor.getWidth() + anchor.getX())*MASTER_DPI/POINT_DPI)); + rec.setDy2((int)((anchor.getHeight() + anchor.getY())*MASTER_DPI/POINT_DPI)); } else { EscherClientAnchorRecord rec = (EscherClientAnchorRecord)getEscherChild(_escherContainer, EscherClientAnchorRecord.RECORD_ID); - rec.setFlag((short)(anchor.y*MASTER_DPI/POINT_DPI)); - rec.setCol1((short)(anchor.x*MASTER_DPI/POINT_DPI)); - rec.setDx1((short)((anchor.width + anchor.x)*MASTER_DPI/POINT_DPI)); - rec.setRow1((short)((anchor.height + anchor.y)*MASTER_DPI/POINT_DPI)); + rec.setFlag((short)(anchor.getY()*MASTER_DPI/POINT_DPI)); + rec.setCol1((short)(anchor.getX()*MASTER_DPI/POINT_DPI)); + rec.setDx1((short)(((anchor.getWidth() + anchor.getX())*MASTER_DPI/POINT_DPI))); + rec.setRow1((short)(((anchor.getHeight() + anchor.getY())*MASTER_DPI/POINT_DPI))); } } @@ -197,9 +213,9 @@ public abstract class Shape { * @param x the x coordinate of the top left corner of the shape * @param y the y coordinate of the top left corner of the shape */ - public void moveTo(int x, int y){ - java.awt.Rectangle anchor = getAnchor(); - anchor.setLocation(x, y); + public void moveTo(float x, float y){ + Rectangle2D anchor = getAnchor2D(); + anchor.setRect(x, y, anchor.getWidth(), anchor.getHeight()); setAnchor(anchor); } @@ -254,6 +270,28 @@ public abstract class Shape { } } + /** + * Set an simple escher property for this shape. + * + * @param propId The id of the property. One of the constants defined in EscherOptRecord. + * @param value value of the property. If value = -1 then the property is removed. + */ + public void setEscherProperty(short propId, int value){ + EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); + setEscherProperty(opt, propId, value); + } + + /** + * Get the value of a simple escher property for this shape. + * + * @param propId The id of the property. One of the constants defined in EscherOptRecord. + */ + public int getEscherProperty(short propId){ + EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); + EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, propId); + return prop == null ? 0 : prop.getPropertyNumber(); + } + /** * @return The shape container and it's children that can represent this * shape. diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java index aeb34763ec..dbcc8069c2 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java @@ -22,6 +22,7 @@ import org.apache.poi.util.POILogger; import java.util.ArrayList; import java.util.List; +import java.awt.geom.Rectangle2D; /** * Represents a group of shapes. @@ -186,16 +187,16 @@ public class ShapeGroup extends Shape{ * * @return the anchor of this shape group */ - public java.awt.Rectangle getAnchor(){ + public Rectangle2D getAnchor2D(){ EscherContainerRecord groupInfoContainer = (EscherContainerRecord)_escherContainer.getChild(0); EscherSpgrRecord spgr = (EscherSpgrRecord)getEscherChild(groupInfoContainer, EscherSpgrRecord.RECORD_ID); - java.awt.Rectangle anchor=null; + Rectangle2D anchor = new Rectangle2D.Float( + (float)spgr.getRectX1()*POINT_DPI/MASTER_DPI, + (float)spgr.getRectY1()*POINT_DPI/MASTER_DPI, + (float)(spgr.getRectX2() - spgr.getRectX1())*POINT_DPI/MASTER_DPI, + (float)(spgr.getRectY2() - spgr.getRectY1())*POINT_DPI/MASTER_DPI + ); - anchor = new java.awt.Rectangle(); - anchor.x = spgr.getRectX1()*POINT_DPI/MASTER_DPI; - anchor.y = spgr.getRectY1()*POINT_DPI/MASTER_DPI; - anchor.width = (spgr.getRectX2() - spgr.getRectX1())*POINT_DPI/MASTER_DPI; - anchor.height = (spgr.getRectY2() - spgr.getRectY1())*POINT_DPI/MASTER_DPI; return anchor; } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java index 1074916004..58fc333053 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java @@ -105,9 +105,13 @@ public class SimpleShape extends Shape { */ public void setLineColor(Color color){ EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); - int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 0).getRGB(); - setEscherProperty(opt, EscherProperties.LINESTYLE__COLOR, rgb); - setEscherProperty(opt, EscherProperties.LINESTYLE__NOLINEDRAWDASH, color == null ? 0x180010 : 0x180018); + if (color == null) { + setEscherProperty(opt, EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x80000); + } else { + int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 0).getRGB(); + setEscherProperty(opt, EscherProperties.LINESTYLE__COLOR, rgb); + setEscherProperty(opt, EscherProperties.LINESTYLE__NOLINEDRAWDASH, color == null ? 0x180010 : 0x180018); + } } /** @@ -212,9 +216,13 @@ public class SimpleShape extends Shape { */ public void setFillColor(Color color){ EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); - int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 0).getRGB(); - setEscherProperty(opt, EscherProperties.FILL__FILLCOLOR, rgb); - setEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST, color == null ? 0x150010 : 0x150011); + if(color == null) { + setEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST, 0x150000); + } else { + int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 0).getRGB(); + setEscherProperty(opt, EscherProperties.FILL__FILLCOLOR, rgb); + setEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST, 0x150011); + } } } diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/data/44594-2.vsd b/src/scratchpad/testcases/org/apache/poi/hdgf/data/44594-2.vsd new file mode 100644 index 0000000000000000000000000000000000000000..a597a0d938d5bc4cdeface5688167e67be2b2740 GIT binary patch literal 69632 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 literal 0 HcmV?d00001 diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/data/44594.vsd b/src/scratchpad/testcases/org/apache/poi/hdgf/data/44594.vsd new file mode 100644 index 0000000000000000000000000000000000000000..abed78b4d627789c885f7eb14561a373e14c4aa0 GIT binary patch literal 54272 zcmeFZ2V4_bzdw9tQV7iyARwX{0s<-!s#wqoRa7vc#EOP4SO`)?ch#Vv!ipOkuDWWl zE0(MU+qwo(S6vH&-F1zKpb~eJ&{bgGGeOm>++ET%xf)&E`o2em*C ztSi}n|GWJEvjt*+4F1an7zhKI@HZPQCRO>LUbt`+_-o7kGber?FaDWh|CwX{)%*W5 z*ZfzG`Ja6M|H}R+ZG*LapM|LEYrycowxjTg=cb0D>j-I63P|Gtm&qUl??T>f^ef4E83 zH}uo}`_AfXzt6US)K3$53Zc!c!-%0vmm4p2@r`8Nf5~pDG;d; zX%Mp^(jor4ZS$Y{_peGmtQ=hU&?EdTL*#E?>3{r}l!gDQH^c%+;|^yo4g;n{QY+jtN^Jmn03(l&%1FWHWn`ve$%*sk!DC3vW&y8F)|IU1=j-Rd zb+T?`JAE#XO3Z=R%=x0fr{F%aPUPYRK_0sVf^35#2=e@O5ahMDf7&MZk!>v%j}Mhb zj-NC!I4CyEcqD1frIX3xpvbtem`Oo#VUvQQ!eWi5lg%rQOP!xJFEKZDeCVI;90Yfe z*Cdy}w=;Y!5EtU*?d3Oegtwo!moI6!|6eTsC;20v`^8FS%X?8e>+pIgf37E=knIr% zEB23Mjcga#@Wux#fWKrLk}^h)g?k~$@$d%-vM<^oNT0_7-h`AjXZTC{L0<5eeD429 z*2p%PmY2O43r@_vP=%pD$vLBPumLxhOYha~HavZ{V1PfPd&cr&AaCWOD_v0g&dI6m~X zV*$<+QjOyQ+zkc^X4ZQNQA8Z3CbaCAJX3ipgKPj z{_T=;05)`Rz=$^yboT-dh-w%g%4d8Q|n2C`oY;JLq7H5({` zw=GbP3IWm4LbQI=n;=~VP*Tur;8F{?u=o3e0s>T3Re`3aCIZ~PeH)xSc~S?qZQJGu z3P2i>mIk7tqJS?B2NQdH`}ZxL2=Uu*zk$u0Hv^)%xf$Fz4_6Kpi9!A2OMp0CJ|8@* zI}3@k4&xwZjJ`nDPn0RcH+&9UczNKQTu4jsA&h>(yH;ORLX$aOj$`0(Mw zvlct@5&nbapE*XtdEJJz>&5T&aeWsh!z5CqKQH9^=WR0FiMNkLzJdkhkQ4IxARw{V z>WLP;n^y1ernUS-n+a2^?=UpAPk7x%9i`QWYV`<+^GDva7PJ_~GiNYc3@<)7TK!b59tCkL?X-HKrh5~z3GP9TZH1aZ@ubqUdU}OcZ=%&Rw0cvm-VAP-YxNdd zy`@&)4{k7Dw{2|b5?n_muMq^PMqWclAZZE$dIxe4Coby5l~R(R>)32qSw;QOY}m@O z^7_j9*f>S~S4H?m${zYP1ht5?+62#v=epPgv%GDyF=`Js-M={T#tuxR$r&;ZLC;Mf^GpG zWrd%M_x$dryzwQDK^xMd577mgGtZkXvwq-?JaDI>58NpaTo(um2aAVqaxR`W{He`m z8`CoGFK*fa<>*qz3zpSf{1RoL^=6w*{lR9Nc|$haRP*i+=)r&C59CP9%eWS0T+1?U zzcQ|bS;l3RaoJ^DP8oNwRglfzyYSh3=}s@-TlUCMQ|j1Qp;Pdv21JU&K(SXEgQQ9% zXsU#gDxs!IXz)KhRbrAV-Xp4<^i=dNbdnZ9SqUn(G=>ciMZ`j>G9_DQDHm$3uh#>hE?9cZJA?!&vX{Y6nW6tL$pbui6}H8y&0#)8o?4Kd&? z{DWMdjb5K^N_iX8Y&h_Yx*&aQbk&@?X2Y2m@Wf7*6^7aEp$p`D=nO`Xe3m@ibvZVe zTbQ-m_mJ(LfpQLY)aE6$Lk{#L#&_Y5vf}nRZC+x!d5PKPCFX~0;Z=Tm1+Q{^VTK-O z7F!itjS=Mp4z%6iTcNdn@yZWfR7zRnPF=CaowmmP8$rc@Bd`t^t>3^?qFL7H{M2a* zY(UX@)`FSH3W;#j`sikZn)(jz{jgBBP#I|>l`fEiLTQ=wfV5I{O>{%574?YXn9~Mw ztjoAIK{i!-YFEMr_oK|Vn?*W0KA@Vofh8s*P+6J~@uUIvKi zrnJk*ic!ejSAO65zr1^PDMFoXOq|#i7AF{-N>fgy8K=_xd~_wt ziOWth9lhVgEW~O<7h}*(kgoheQ>`h#uW8bBY0Pwu9owz}%6Q9Pk(&te6!m(ExHm1Q zyhcVf=n$`lxTe_G6bl`44eIhoxV@z5>(@b)kI2G?O>k>(6D2R16CDT84IGcA zhfNP&FQ+`LU)T`7+^eCtspmB%G<^BO25XNdmyW@m6c+=3VS|lF)1;25PRb<1!iM!+ zkEVqkd7YGnaKo12(X_K;M<-<`+)%>nJOA8qwUeTRckA!b^hXE(_fEC&m4jxTe^kA6@B?}g0A#*4LJ(@=Brtd~qH8X2DWA#sjaLA%Y=Fbjv3Ri=-yuyr4SUv+WxeavqSwGHDU$e6g%w$D^Hcs^c& ze~+_{j(Fsv%C&ZUdWG+5?C2QdZ1WhU~ zWpt&5{JO4S3*O_J_2hrZ4Sa@`$;F{ox;7xdMyI`Um%#>SiJI;v> z%N(36+9O{rVr>;Yns+{|7;(NZJHG5m7$`<=E}jwABVtM&rM}WIDVQS7kbW&)D^*HO zxnq+oJzm;VO2mvZ<9_p*qsHFDO8s11l`!o^gEQgn1eqK(j9F;*c~B$d92PPExx zT`2Q$xVE5Q`4QYwmB8DdXf;?iRXJXU0^WY>v=rG9RCa_SI}jT-l`WKtJd`KoSL6po z=fdet&IP}OJ~jN3VRzi__*;>+6-{_dS*5H~zEaL#d#T9hNyIn;*ltwJ-v7;LB#Uc; zCfTYKmlXFDjfzeMcrNX&G*da0yVp=p2B>0G4`--Q)Y_d=5=c$5K}+uKwXz$|yi7ee zGUrM%#{F5p^5>W&8XrldAvzy*+}UH7ly`;o37XKv9W*`&xjW_|jgN-h7hhpslHHSi z=$9Qhj;cOM9cb+zQZh9}}B4$z%Ac-k6m)cM?qJ9{%G0kH*Qr-zYXX6?+{(I>## z^=sG9tfM*79M^7$cZ_#LX^!B4_W|#CMTx|ln7UVu^>?Zx z9b4*01&-omrb_c~698DhrewlzWwdX9e@r%hX`qsbbp>WoAtX&t0{*W_U32cFOlD zs)edz)i%{G%}GtcRm~&K`Jmq)J_+l+yUY3lW~v+T$7-&>?#^YCu`U&9*f)7vUJp+r z8y@_1r>t2(0bT>;-`b9pFX4sbCx?PI{IK9B!O%%12p;AiG9?I$bKrq=nird@u^A+;KuUC~IKdZx=9v)MGCGD|xE|>aO5zyU5?4cK1 z?p!mxCgPgJP$&eUF?w6@Rnhu_UOc8LC@gwrWz;lE()G}oyHRoAS6(i@3TF!j>8Dl= z5%kv5BrjvGh6|<&<_PfL<%_^YWPq|8yu_y^+Z~@h|9dG=GLJJaGaoS1MDLm3hz2@* zI6U5WS&?YKn^Vgvlji;`s=eU7aQVXD*GDqJEasYpx=UM^u4USb+(lzWKrDJ7do6n} zGnWsP3*dxv-6cIK1$=>@;H~td)Ka!Y z@U38~63kIv5j+%dr5^+q_#oU12UO8jRT@4}B~We0kKtjBV1fK8{toZOS)!Zr-{d+u zKooOCgB1aaa?uIV712Xc+8Yr_!_Gpv>c@xyR?@-J0Eyw$v#Pl1krL(=q+jRdXl1ji zTXj~tRryQ`ep1#b>y@IKP~={HmVAXAR6aa>{PJ(dW)qtl~dmlbNQaw>UQ!P_3SA&h}ed;sn z8|o3d-_`Ltb`83=b;QtHi!b9fW8$iAYel_1;|7p{4C1gFhBAdUWi|fNaZ+TcEN*Sm z4DkW}vwZ1QCV0e5A_izYG`OaMxIpA-_`~K|I`BMs7e@Arvc5Btk-*CyxY*lmwe4?) z-+t>J0;*}1ys^3`yd+%@FF`T7hEb$Q$cq^g^^5ga)nC;t6{IKxZ(dTi`)sa7Xxmou z_u0%_t&5V@6rr6#XLMbq%wKgLFs{gB!@NiW<%OU{K*hQER|F^}BJo*x7Ji>->Pla5 z#~bXzPvYODRU`Z6zr?e7MLe*XS3SpFca0|%X?ZiQIna<9FGMXOs&sG7Ncp|%122J_ zsY7ZGh@xuFN`H|)ll~zs7>19*on^yi5wexI;3N|)maUNqc`-b4@N1NH%D@rc0J(=8 zm$&ig0z1J_!J7evf0--WSnW9{%ELLt)9#KA(v);*j<k_QZq}FrCFibq}i{rWh0!QG&P!fO{<2cv(*jJfzi4s zom96#SEwt~9njrW>Tl|FN<@|5%7anx3nt-6T&S6%itLI7Nyyb1#C$?dtS9ymmBcjy zdqQZ59)ihqWLo(#!^C1(wVSZp*@CNa`c)`3lUK($Z)d38~ z)vI`0cojUx1>POIRplkW@pL>yU?l*91p$Kbf*FGO0=ZzlV2=Q?J}tN|cq(`&=oPSV zJ`ViwaC|C02Va7JicYYGlB<{Ns~d+w0hj)kRw4x?R*%uiy0zv9BPjl z_=t1y`XJ^UkTg>&S|BPEm5Bhf*P@%E-$XhQA|*!z5EwE>Dv~BjbEGS!-$@Ti3x1T| zmTII1DMe-@bCne-aTgf(}R8(8l5S4A9YLY5Jm8~jLfz7Ies`IK_s^==b3RPRHt6bD0)DzV4>P&UP z8MkCg^6_ugG(Q7T~A9w!^xOG&?l^f*}BELHM$)-mF|*Gcu&`;>(rSN0|*bIzmNbiL<+HxC?>WM zM~I8WT>`u$+6X$+jyaTh{{AM)cxKm=hn>uYOo=TTHq=jDZMC|DLpM) zE-lDru7=qvCVos>B`wKkf|j@>yhF;6*~{EzV`bt@W`CH_N|oiyN@U;5s7L3d7gpkt z-xofVy^(#80SoycxtBax9w$$eFOq*F2ixVx#jB1EbTLs#Jado7#P(Zo{D?|;QFT|P*)s)SH2K_>ewQu-2eqeK zsE$!b_m7FmQ?FKITh)ivKdBc_8*Lc$LfxWXk@uF#zOwo)bXRUKzJvu&yvEq3JJ>vS z-|`#mZ-2hKt@hj%>f^Ed#%(^>Uqfg#V37T*SFg^J7AvY0O&WCR@*FWake$_NYs=%J)97AIQqV6X|;N7-2{$Q30uvZK0F=RG=#aQ>nApwH$|7Bn`3IizOuyhjt)R4 zsb+f3yc&a!Lf8=hG9z1XE|jWnl_xUayon#3djPzK`#ffkqQ z$L>O7Es$j94(D^14tpdEj_QPJU9GM`_XiX30*?58dwH-=>%FpXdC8MmNikD~gRpS( zdJ!{`d3^X+-s&+O0`NfaTJTxQNdY}GGZOkP>v?z;>Da7F6@Ql2Ec$PAapMTY}sgAWfV$`%F>!gYH!|TdsLc z>hoJ?RMcF!_4r{@CVr}LH*N~^!@w=`UQxxwDtu(4db_G}mD{fS$96t31-)0R&0pWI zlHf}kED9UdNl6*+Rc_|$f$B?_Hl;sy5S53VnsxRynI^b(`Ra!A#X0BucPLMro^5hE zz4bnovFqWq@~hSCl2g;l&lIJ%ZwA4 znhl!0n$sG)fnFl?Y2DlxzwK-2cw%m|ti6tgFr=xwbSHIJb&viCtbM0by8<$@tz=!m z7`-K@`Gg;F<+8~sZq+J~Uptd0<2kG6JiJ;PqAop(c%`W`9<5MsQeP0*{T#C!p- z2{&euDKKIDrZw$D8$b*#{g?#NY`Dv_eDRS?D5s-zNWXEQqj4}tl!wCT)tnmmwndi5n8}ne z8@g-`Hgu(JrS!Du^VmL=+lYXLAbD&W+HIZ>sX|-P1k(@~*3!&ftW-#l$#r41XKVGn z8(EO^ln|<7Z|0b7=N;oT8&bET2R(Q=@9Tbr4P9niDKu28k1*qE_ae;(tW^)d=3ZUP zT4Gj#98NQ+?lfs5DDuuW6M1K6r4j=?j9LIZD8$kw{uWubokYTT z^f?!D%o2E$0ZwR$rG*5NfFPF$0_iGTgvi%)5=dtvP<%tsiPjOmzJ&Z6axQ~HArvK$ zN7V{jkwVCIB?N8tx4>leY64Ld5+-uy`{(PBl>{O$2MVCvx{gUGDu5Ctw!WVU4$%ql zEjVMM@2xhWCuk)orkQviL3*}3MD`efs4R`jqP? zB`gbffKx=zDy=>aPQYgXA_riw;blOQN$68Q>T`Sa+3)pJ5<#8`I-d!0+x3!UCYa|0 z7QfeL_vo8BNNQ(G5(Q4A=2uWaDxt?rKsp7^4|VF(nIOxTnqdMy^e}Va3{Y8wQQ)ZB zHYjp%lA^rf?chBAooDwF|#&slQKGU#9-{;wJ5dT0Gngb?9A-=E3=` zgglF07K`#K2?g{5O%MRWUIquri#BJmoq(;q?OFu2;(XH&X0Gih0?ts5*qW8S9znMw zrU*hEuojs?&07Z&us1YXx}85ImrGm5q2+A?#e??QtVQVW>4U8Ltwl`sCD!$4Xl$Ad zJ0>u`eY_T}rahwrp^b+gABQmQ*Q2@I09Vw9@0?>Bt|qK5?Y$7Hig0Z1Qj_cdC_# zcU}~a#VYW`*mHcSc|PeSwp;9``_Sh2(DF)Hq2G*bHk7Z2b?R`EMADB%Fc3l1+m=1S zjQbqA6I5R`sMjRa-+Z`NuL2~njifRHa@=AepU(3mNRESSN=M+NgH%1Qpinp^)KfB; zlYoan(aO&Pivztf!0b36C27cR{=y+?!U|!CZ<;V_h%hX)Obzr}SS?J6@D)xFHXHVCg;gFycWPju z>yX_}WK?&L$xWz~m#Rb&y{ioI^rg&G$56zv`?@?Ac~T^cYl38h&3_x@p6!J6PP{3^yu(CWi|E@P~tyGAAE#j{u& zHX*y~gzR!BWR%w)CJSaE;6&ZJ-XU)|2^*O))xxr%oFT#;Vc0|vS1nwWBD@!xv7TCB ziZIxzRMU(xwEJ*sag)BGOM;}wna+kr(#Pd0f58Qd$xZE!8d^kK_6ptzLTT)Oh6CL=b?1&R}4s)jMyWgYc~2ENiM(pF)9cyGRC_4Z>%Hp=HUf8QDky z@{S@#hQV>^lqLyDfg}cmpM$EAvJueUY4yC;wzn~^?tgxW&`@M4I%m=WnCc@zK}R@0XgvdL7MPQLsxG*RB)|+@0?&Y62gNd zJFPxwf(N?E7eocn;RNTiHp!f~E%3h1TK!yKu^#NKvto}%?-UjyAXgH2OgBI^g?d;(c^uMoNT|*bj zBgY@DzK9~9f_x9FH&v@&8sdS5t`s7z0RZJAK?vnb6=D5g^(0^F2dgLfQa@Nd$(Q=U z>Pf!T4^~g|rGBt_He~&vIr!JA*hW@yEcT^})~AKY=0B_WCzba<(K{(*@6^L)eOedG+NwWJxHjzv17MoPs62UrNZEeDYAC+5hCBqMtof@fQyjQXaJWpP-IF$^&## zSFmvjTE2v_+fguTyd?zuqg221v;S)H(MFJOYD}s z7q6kOqrLcuK+hGrv5m}TbAke>gGan=xxNxAM#Mhq8<+>i!+BG=$N)W#>|Bi?rod!5 zquEeZ7tC0&4)Ne~%Zv&BQi_@fDBzTY~4^b=~C;Jzq@by-z(h_vQ=YIdOa4T+Qp@*LRfUR3?SxWs0FoPYy>LFZMdge;_ zzQm|utoO56O0en_xCwpqt>`-DYGOqWpwDED0wu(%JYczIJ?jk711(-C?Ak;?%mx_j z>`aiC`%#Y(`gD-`UjJKGdrKoF zO^E3sTdLKZF1H@pcz)PAR9%$#M(Ao`w9xDrKW+LF5Gg?N`XC!L=BiMPAT^ z8$XVH`^f9ETIg`Ca={C)3lIG7;Tpdq;s?X^^if``5u3w4dB?pbCM+*TJSKPp4#h55 zo5y3Y3WQ$5US>rJntjZwDlh~I2|-+|dhI>9r5x(I07wS(4KlZz5v+w(VEV*{aIH_J z!%i4kg3$9!LBel&9r}ase1LgyzO9O0xintA{k5c zm--Dsuk!0ZAa@4uDT4*HDyc6FCPqga91X53XgwuS!F@pj92XWtHOT<&v&A5HHheP( zs=<)iWH_-*>^mFeMa>Z>r;BAECrLa$Eh{E5Iy*(YBRVll{98tnI1_l1+65}Ep>d!1z|Qcf$39l=OppN0N^ zL}SO!L|E$U% zBK3RuP7`Ao5EER2#%)Key{*0P@Lhh(-7wk5wK>-pb?eOS4j$r6Epm?;$}YOfKXudX znPc6pti+~vPV(>)G%udRVC}N@wi|cV-P^~z6iL18?zLgKkFQYJyP02p$ksi^artEb zF(Gm3RO=uiC+QB~ak(#T>l*A}sYQ3#)^7^L2TR>0Th~nV@~im9QsRFj=dJXP^P$4A zFBVWi5pq{B84d;EPkgEBoZFSeq{4NpVlPfZY2nR=OnFKvvR+>QVY2_avrG4S-^waR zhF#cnhkppY>GsQw99%LwI^A?M{QHtQOJ5AEy_>|AgMq6Qh&5SjqqFK_4n;q%iPnf0 zK><%(-Ye{N3r?_`1Y^oL6@0-;bS-P*Y!F*1^jS}VKPTdL(xAiQ_s9NT%hAzJFNMnava$Mjsv1~S&AG2SalO?_vJyxrqPp1M- z&}w!42To9hIs`PF990Wmfsun^@}}BlH$}U*!v&2fs2! zy?kDUq2Bl_F`KbO9MzDX?K|z&!X$fddv9SW+iQdO`iDWmD?*m{Yc_z6=#J;w1IUYr zawv?u%wo;NAaU64kh9`twIg=d22E}fn}$8W^5%%MzP{_2dQ7~!`f1~2|EXGCk1n3s z>em9(g9AI`(!`70cWK0))gXZ$dPnTIusCLK%hN_*+N>lZmzd6MbsD?JA{N&tNz3Cm z69+qDL4Iy^vKTEAcXWrPSbVSfKx}I5iK^Hy&cyX;`^Cvw;ylK!n4`OVTdAXn$Pexk zFGNo=XCunBb!b9Aadv4;%1$Wx7?`vZAoKZNrOchor;U~p%RIrZqS5T1k=QSKaEA5?DDYI)p{^G(cFHaH|H=56CWuOIW0eMZfz<<0rn z9^~GVn$H8hoM01b0xj2dQrCo+w>u*Q7p4pBcB2WFv2`&S!u{eOqYDHZ1Wy~&^WrK6 zr>+T@tpmpHVMHd0lMD7wMevCq7p4xuN8?d}!Dc-Eyr<%fW8#$yGV!SQV8P32>*5N& z!w=zX!!1}Hb81YIShl9_3Ea8trTF!0y2wuSv3=5Fe*X5D#fG|m=#;18+p!~d{~)T4#J0RVE_fqK;87@5AgkpHyq_H>dj4IXjG^+n>_oqX-7IWz`2>T|b5{Z7uxOw3hgZ|qrBzGM z-j_04ldI7L=92yxXGnQk#_kK@S$J50_7sgBNyXQ6G7h&RgWw5uehK zs4)!*O)qpzP(ahQu`Ss=T^pH)MCj;fI09$DJfu0|KGvB1cC^-Xn?y zvMT1#xOC_g5eU`9E`dWy;}*ibCppU;N>6e&*wOO(vrX(^zUspSh7*i?NQR$u97(ZUvq@- zt=Cm3+>U7co?oN=I4(5&TzMre(-S*XQ62Q*HIA^v<99g{gl;dB#T@@ECwvPB-D#zs zz(zIC!zl0in+G|(wwgyE?>KaR-z?g1+-J8VNJtl-la{~ zpQc9Qq=)VEbe%}&!_{87SHYSu35Ag6o4*gbN*4AIY zLc4mKb`}>uAIU(fC`PI4fI|F9g7c_oW+8|Zn~M2_EHXI1GfNO z^>hMb#!(7NX!U;~#aRKQMX%N4P3=#)Z|M(7&pHS&>1Yxv2L(Ye0n-ck2hb#h;S8le zm@%UFD8MY`u z?>V~{3O4j3p<#RZmYZni$Y^hx349Ubb=&Sw0zL!29O4LIE;->SwYRA~ylrRA>(=)rN4i;EDCjkv z0W7VMUTZ72eNJD~ch^lVv5ECjdYge#4q_k9k3ym3hdS1d!tPR=@S-j}ilR4X`Gw^T z4h`E%SX{WRD!qe;f7~%(iYNj5#v@Zy-F>Xd+=dc5osAlF#SvR*p<#t%D{_gQpc)US z`q}H&hVQu)A4xrZR%Y7z7=6Annpb>d`mpU!#7SkaPO@)_m?ZQpKsI751Ol2C=ppmMSq1YAmq< z8`w(kiJ!E@s3hH?(0&a2G9H$>!jbx;Pf26ocZqhZ5LyUtWCdvjQ!% zkdq2@5f2|}V)ka_7W3c-9|Lpg--K_W^@fIt@9Xf4x|>jVW-CdJ?K#pI*a~gV>G}Q* z9|M=q4_Z}Q98o~)4h@4!UG+A1)Q*Np*BbF(LwiRO)m{pV%UJ z8#sKku6(_?X6I|DJkYpIO5{PEviqY? zk)W;^%#D`l03MzX?bPT{iy%pHCcAeev{rpK=s7W_BG+hxA0o?z7^|`>F+RQ!s%m!RC7w}vi`jl*O8@B3*2rj#%>Em# zQT*c26SvQj*63}|GPOoWY7*_sFxCHWZX4m|5gPZ=(Ny?62>g@&+x_sr`EPE2_20_k zUk^KG`C$|nx^F|xpia#l^GT&F{t1;D|EF1kQ!M{$m7qp#|HtlI>t3V#cH)z1GOE-S z`|vORTiXD)T1pe%h1arC9jQ0nqzk7p>dpH*w@qeFoZyjJ-A(E9-r8+S#*wZY@$8e@ z5aE%1=CC(DGO6+`>AJ1B5jpvCc%SF?GktMiXtFh41PxH{$gO>j+b6AYARV{ab#+Fq z`90r0`jYmBC^NLrZ?oS{7B49(jUzJ%uc+fXtz@1>*-;xtv5+n^sTp~FD2w)Ka9M-BaUz(;}Pb+i$^}dcm!n`FC>W4J=mW?&JAN#K!bSiLvl*K10>WyAd9wjG1|Ub^H@QeMi(&%fhkC1f%teew3<#1w2p*UyQio^UUynanP}U)6WlJ<^}Q+ zoym@_hHsyLB7nh^4|{Va_`n=$0?-ai zy@?HlHEAS>HVq7#;^;INcXQnme|clYM4x>@uP+H&uN)J9Fd#a$q1Sq&fX$@9I&#|{lYIDrJZ}Orq|K7^s#tHJqI`>TcBaZnV4ZgQ+1P!q}H1e*0 zL3p_4D6OgeaR73-0S}n4OLQ{GZakYZGzxq-E>zSVHfx-{|78D~@Yy)%CtZU-GiWSO z_~|yS{w#t6IC(w{=wR5R>ObW^ua8bdfF0f6)?V1{{Q>d>dfFiWbDTsvep*e$%$M)2 z`?y4<*RzpB{AXSI&ORzp?rHgWWRNrD4R-4LxI`&;q7mgX_=@o|l1ubVRU|~ZWdLRm zg}oo)6u83+DDf&6_!+t!6wZ`xlI6!D0UN<}h8XEO%2dOTFx*-eix4VL_)!_1{-L2I zQucK$qOt=+C@*M)`;u7Xu^*h94K>g$mc}9uBObwz&>86OELmxzA;)Bx0quLuv&BHn z`zjV$KNf)cHzG8Lx02UlI3kb5k;Ovzi5U%gi2Ywkn+QJ_uO{a!-GA7K8l!?!bi3vu^#G+4(iE&2C1j(3~NJoeP2^h@ogA5(a(%6{EP}a)phMO| zzB~&OCQJbQ;tG)u=@KL-qMf=JSv?)D(BNUPbQNKye{YfxKU&lYDF~=WZ&H|#>;tI$ zBeQ5XoT$r3vg9iXnn}J%{u%HQ%{m4@ag=XjvN-DsbsjJ=0VZcb%mYNf4g7|J3rN-w zuEPM_N9YV> zq?_VHJW-Q2{$}H^bvUFRp6h8&YGwA&4r|N#hiNbf-===C6BBA7h>4V>J)29O| z>YkQt`0LvaoT^LCitNIofp1*4GV?Cy5R- z`j`$zqJylD=|B=4elhF6-^g?zi4Lso$Z*d0B+~)TJX?vmThR`~EC(=f93(mbOY1@& zYCDEo4%rTykc>XI!%w!hCXgXMS&^Ekwq;fDpt;Y*@snf^^7iFo(nj-{42^{;&CWhe2k;7h=Og$ZY5% zHc-8O-@Snh9x$`S@C|$8Jm`Pd-{_E zu>jxgNCar?$WGipcv*@mQZm=me_0BoJN#joH`|xCEM*b9WbX9Tf@P50P%?Lk_gCrV z?2_5D3reV_2+3-g)W>QtYv_tGvKrnwzH{8qAMdg**VD&!2gz!v$lY*1c<7m2YRr%; zxjXo$iubska+H&-hUVBi(dc&|FCNkw-Z|<6uDVgZ%^<RL56P+O(yPMY-7P-dBYv!t~wbW7s3x&J}Pye|~(~{$gA(0?%6SALQvBxc_ z5{=u3Ki{B))Bs==jZ~$Q--@FTEN7!gVek+ub32=0bihQ)(C}*ulqg9L*e_ljof97{ zUM|5v+Jjv&p-9_%~0I(O*1G)!<(K&te0JC}Jw0Y$ezdgxr7rE)oTB(ac75GYQ~7SC%taXjaA^dj~}$R;?7<(UPGvs!*gT_yI~9sR)5zkX_xCRelS z-eEYkZ(65V*dT)<~Y%-Ilo6O6K9^jo3>0GmK-6@+Z*#}SH(T*aZSPH zr&I})30<9T$`47^=y#A!kP?$(qyJcsnS)6-`uQI)^6kaw2jz`nT^M8^5XH5QYwwxU z#LL9ldBkeSQry9Kvq!<>mTtX0Rrlj^%wRrTI;minT zc4N?QZ&+Yc;c$`Ob0OUCUUsA|_**cw=-Y)07we;7;}K z>j`o0ys^Ah_Xe!J$Dl=y5+^U4x`#x;2cyTJ;C0_7mblwSZ#wsxwv7nK6<;k1s z>j@=Z{REg`YH@8y?Yg_vm{DRGT3sL(TzS%7J?qg@fkKeoIBBRJ&CiXQo54xkj-HV~ zYJ-Wz=&O&Xr=DeM1)eLPjR#^#t2iOv7Yp{+x#A-OnRRYFDgKD~s=+ib8!iD{%0b@hz{D@fT2iIY1 z?@eTd9O2L3403{U*StV5`HRIt5iaYbBLmM?u0p_1(i-U$hQ*j{W)YPs8#w~3LR=Az z%AgX|*!=V=`c<@<$B9FeAOE5*jLg8(nhLI;UmuWn;iBH-hshj}Ab>O{X_1%9+?H*% z=8j}cOi>Me~nn=ChZ|+@KF(&`_ z{U>iezwdFMXJkElXXf0QGv%E5%{ke?I05rLq%qPZcxHI`;)!h{B9~3vI4LIb*)@X~ zPmFSUBf{;C$QkKvXQ$6dm!F)Xn4L}%Klu~I+NqUd{Xan(*zquScaG==W zU|T613oo1ayydbB>u=(?>p8m)9cfd5-DVg?Y<67xMKICJm|_mFA%7gqOeZ+p-{Uby zFqyN5msf+jF!cG)FtC$4q8mnW`QArsEWc zE$OdoKXZr&ta_>z8b^=O0+3b97+cUM3uWJ*JLRoLp2DvPkE>RA3 zHXip%nYJz{>+HNYCs)pZ3~9!5Uct;Fob%qO=JX7$-g)7L03WUCoYzL3Jr_pw4@+;& zx9w);Wkq>A;YFWN(IlAO&niEr&hG@BCY9H9XEmGp-;dbcSv;rgjAvor&wE$*@^g^} zHb#6SoqivbxvR9I4kr-P&F=JaG|?9m7^@T_S1MD0<(?D2(FA;A@BkWw)9SU_%PoRpOy#kjP=r*;z*VD1YWpU(ZK| zsV+h${iw+7VTb|`uh}l<{b#wiplcS&pHe%r-Hc#TOaP4JqMW~n6EJPd(6J5M_tMkI*kJT1g>!2^hs5%Hl1VHFH!-o44G~O8wt#l`;l)yHgWZ6Grvm^sV(}8CxhE6N-E7Ex3}u&L zYyd9;24TDi7FNz<9%hPP)Awu|(c^+3q2hlPc3b;Z?)@b=XgzGBR*G-Tw~h5$qO>F! z)btu;^sTaXOhoOw)O}O-=J&L(*(ZIocf{2FynwrRc))>7Sq*L*1xl^i`As)_&09wj z4(=G>?sBBRbwfqD?u?b95#nhbNEpaxM%YmQfnke%8++wQCwT$m)@u$EN?-acgtB`i z?a73a`o3uHX)(CNFbmll&!2Yaau4XlHa9m1FHwFOW~D!1KWIVM>G|m+uZ9t2SMQ#N z@Qe>*+LpEJ6W%6~-l(=DM(n$A6Xio--;IbdF>kjtV&6-voak51%{NinY%XbVqCG9B zpE=~%v&o97f_mIG;Q+Wftkd7_!tGpf{{_sgM>*?Zx^DYU>Ga&$(QeoDuN~8s>5uFa z6&_^hn;kPu__4b%Qm=Twk$$U_o@lxQ!=@9tFMfAftSTjc_cgJtsKPA!^GQy1KCb7) zwvFO8cB-nn6M4H|_EGZo%t{763%wj+E6uW2#9RFeUi&WdFn_XQKXOfnZ=qF6MaMzeZ(CXYu@z{ zZGG2AoTC!IsgW+yQ0Eo)+uG6H_TROmKic1HH<-dFkQ-IGJG)U2_a%OA6pn%1XsY{6 zH|o5=eU%&C?7r8H9(Di8Z7{v=e&3CXbT)mcR=32f4;`or??XrECiJ1vuj}6KLznAl zPtp20l9!=BwU~N$-;1~xakZ9``DVXgKY&%|1i%n^c86;mMygj_PuIB6y`AGmb&SIT zqKO-w@0?&8pWZGmB0kjMmqqyUA^z@QgaUjyjxWF9Z!#-18ikrW44nF1 ziq9lh{3YI=q0<8Jse2o!%`qu+Od)+)AB3&Q|&M2JXZxo&L>FqM|O0PJZ{W zH2s8hrj84rn9;pYizlMW~OgQ%6iJBVrpjFWmm&XpcLZ%aKw8?~eA*+ywewRW`FtI8q0M(g1jzgO%r zwwfJlNIe8#+z=0KJ%DjxUP(?KN%h*v@c_p8dl2#kP@-6pj&(_AJEi86C}IVxL{}$v z6m^H+BYnXZq6H3w9Ytxaoi50aSgV3A$=-bvJ0vA(lCM(QbW*se^nN?EqBb^Sn@$hp z2|;T0B)>m6h5AdPmRb4h{r78*LDbHE)m^^fH22ZNqBiHhE^5tpA|$6V6wj?YMmfzgrPM5ZBc{Uu_e#agu$Z!FGX$jKOkz!#758$q8&uaTR zGtTzdrn8ivFq~YQQ2r6}A7z7SI>Q z9&i}k_9KXz|L5x0*G}dRJ}IJhy69-(gW&I*MQ!ts^H;r|a{h)OYSw>I)SfP&t!K4= zVgWVWJe(0SXfj02Ys!y4nKc|#ag$o`e{-+aJ?E9k)l>e}ENZWRVRy5A%D6H?)Y?83 zwZBy@_$Nfoq=9a<4{SOd!)<<2MC}m`)OpW=w^pQ_IVFgi{4a{yAMVvwHfx}Jl_E}L zR6+OYTU5}c&<Z%i>z{6%4Vx`c`lC5V?bj4F}d zPqO+S5H^z*x^!9h^zJ>ly-$j;J)(t{|7O*1LrSMBg0Lz7qOkqp%=Xl__FuEDnY7T| z@BEx~YZ-UoNfEV2w9v0lygw%(<7;CvNEUT5chO)ED63 zcihL_rcCMbweBOmV!!D4Vd-Zlqy_&5x~9^IuK9V}cWE|bqK*l8h9-BFg25S{`3d)5 z0kIjkb#$ktU90c#kcLiSs(#8O=5!$y4n5gr@+~be8z34u{w6y7g?zxQ`$nA2Fr&|q z?c#PPlpG(M#=hh2DL~pBSMfNT7a7ooK^l0Z&2u;{6+jIPX;akv&7BaD~CdH8~# zc>}{Q)MeYGOv-2GsygOKqtb1ckM@(kVOQSxwshQUzggKH_YQk~oIHNRrXLw(!OsPd zg`;hE>UugH(Jk~BFbo`7zAwLIy9nTx$ZkB+p~)pgGW)wOMc(dB{X?|t`&{r`aP4pe z?uP^1&|k{H4g3YVW^lu4hHhZsh6kF!25Bt2^S0Y1WJs^DCaUnL{nU4<`^B3P!6Wd% z%il<62Cd(DCfR$hOUSRdJlLUs$l5yJjN*uZzyJKzWN2BgjJ@~n``kBA%EJAKp(W*= z&pXaexriIcOd+v-I-CBj3->>3XmQx|+-n2xbJLy_QG3MD(&yPt-Y2^bS|^Cw1I)h< zuiW`HqW!9;hyC=we%LQ8-7f$6(jDD`jNQ)({-bpIuL5{v?B4yNtGfjtBChn{V)4ah zPcU)XOU*vNq*>n09+P-fY>M_iW?^B`wsBX%#R0!w=ZLkMGq_~GTx_Xi3$#4XNk%Bt-6o5mg` z4Qy$S=9lK?*wb_1f!nHiomO7jE~ZqtzZ#q4ONhS45dhg*pb&3V>t%q2cF}7uw*X}8 z;0eh_{Q^UEem}H~ze%48=M)TnNQIm9XO+AAWgp;ie?J}9HC@taGxx9V;9h+2x|QOY zKtGqRz#eKA+OrMXasRLgn_v6BV^it*%ui`>gw!=~GbaFJb^6*bPD@q}@nA0Q--tnSa=zfs(8^7jST5z0aQbWOQvr=Q;h9 zzxEX?3*zGsrUfT^zMl4WT!VCW#7n#Xm9{xeJO0ba!)d`cQ$1&f9VavA$6>F~up(?* znAY)M4GG_h-H)dI7xoG!0L{7)9*0!3xV2Bp1pbKea^>LoeG|K0KPpV%$&XCn|1ntW zsp|E=Qoa5XSj%L)Oi1ki`N%ok&L>6I9SK%Tc!8m;T4o+EZEk=Vi@gyu7<-^NHFc z+*ePEtUY49yi)##vMyz^(T~nEe{ouSx`MVAwSPk0Hd%IVbY8ap)F|%IlOk%5Sax=u zk&k>o#pomk zIyz_QkISmndo^UM-8-2ZHx3W)cXuCdxpg{dracl(Kt}u*r80k=lJE)by)ruCs5c_y%+C&@2yByAAE{E5^<#3)h^lj~^1^ms5TdfGYO}uu^@{KVc&A9_NUa5mmb+Qo+M;{Ih)gF>+zn50ki4>~z95u=5 zMdWY@-LG1-BkVxf*+H{&yl?nk@tV_)2Ifh(HLdi1RXA>Wpbb1j+6VEcFzW~Gzi!4! zLiCEy`}Whlkg;G^-i=XVS$ac4&>I0GLY4*2@}wExH5v zAaWa=T6sBa!&Skpx_a|sPsR7TV$YhG)LvdIVX>d@xs1P$Xh}U@OQ53cQB>43_NL&m zD({}*)YnFbRToPxoVSM;4n6=LS5Vqxh2;E?HHcKgyR5phr!d{Oz3(?&uGV(0E3HEK zyVP&f>1EEJkPu49kM%47UgFyswA($z*K!C$JPXVH@NR!9f`fRy9?WuR5Df&xVmo^3FZOHu3Zy(&_40 zgaA9Y4#S*S!?!Bz2!G_1E+ox-I9fMeYrhW;eT`{i@Pr#T;<4>t;H=JNK?d5qCPB zzO(=?wn&L+q{F}GJPXe09=hrIP|Pv&CH*XnUVji4pakKJINc_*h2NC$0mpXB3WGQy z;8?!y?DsNBPmD1BTGrI7@SQb};yg?t6K&ljYwD_^o_4mFiT3Z2@#d}<)Z(f`vX|AC zGAc`ukXJL+W^lFBPc8Xgwxpk0y3p=+IgbAjQxTWUVkGdpb-@0trqSvGLX~}NWA=uW zlfTfQ%J(z>Vz2sYEdq}TZnVU9&Z_ym8!g><`;rzh$6nE{sl+6vnC?;b=$UopRs|or z_lH{*TplNC%0}4T7@d@?mXcFaQi@$6RO4;q2-M>BeyN zFMAPxQXdpdn*2$_H@Y%J4AU)K$Wil!!{o{Grb$wSy?romUJjQ#Zyq<7Q1im&oSA`> z_adZbJ}3Kt$PI=Chc*zhMfxHAT(T9>a(l}Nw}xKh8h`xLxLqUpxLw`EvI(_@PKlNG zQ_0vygC*@uwhWLb!&_NcAWk_PiZhIDTVH{Q+bmupX^yCC&R?}(z=yJs z3PHKLz(&G{vJkG?-!5nOCI#==(d}@J8Gh#3y-6WDKnw`D2`ocUU1LPuR*^04!QpIY zevUK%Htq1~jkk8J=B^9=G`~Lu-*7=UsxL{d>bMBO<7{oUZlJggR^BWvaVK$2Fky=? z$<*r45hWKfcS)AHB5q$(&&8BxSt}G-&^i^?>)?ZEjU3S}gH-D=1uhLz=aCC>l0|B@ zq*T_VK_p(O90GJ%mj!2Manh#YN)g1rP`JgoTx~vXwZ?KWwOFla>hK6C6iK1n(lbn?zC!=qeSEf2M7|x7d!5> z!jYsqDo(3*x#NOymmcqlX1V9NJFmk1+f^)-hTB)-^O9xfbc1iUsW>Npt=G8)4`hV{ z#YzkHQsp{z_kY={vOl&(|T`l^zB)~QAL zUXlYni+j&&Cs*F@`>e*HaGm;M$D^wAD(OMH^Qz#>_@?z*!y;_?EEZjf>1#IK>3Qjw zD$4{k$QSCi1wrNozSyojU~wVzDiQ-9@T(bSAcf-IsD4dfi;e!C&SgPA_~puEWIY+Q ze8^y#xjLu7Lm#JB-Ef-Xf6*@AlUgMY+i0#N>sip;bIbi3^Ktn!qG@?F_BFo0zZ(s_ z?*38_-g#-M2lK7*V|dR}kI+Nmqk0@*4zKoxk3JT;Ct^!_L3njU{IT${d&4K}ZSSEi z^*|&AMVxl)ffqAu5|VC)Cmjf%T;Up**xqYbPZoQ)=}>rv5PG<-y+?E8VRzz9`d-s} zTygxtFDrk8dCKxYv51CBG^?X5B&&xTD*a!OerfZScz=K;94$5Uw2I0cI$mkvn^Ei2 z=13~`a&!I6NBkN9hhsKHwV97#n@;Xn9cNA>n{|E@v1E5wDDWb~Cd zAJw@*?VRYEcxC#1rvvNL8$Fk%sM^gDFKRmI(Z^pgzhk@ExRe|b1wqTFmY$9`oMLCe zg+gebNz<15*9945lz2AHJnHkt8ug3HROM=gbiKq}=`wrKEOq~PqUTxAQS*b#XJx** zF!1#^7glF=ZJIg1eUx&Lf~-~?Uf{Ka`t?&~eW~z$Z@6lya{ZS|x^%T-lDc!xe8*cA zuf3t|^>cK)4{lWZ+P<$M>9QiH15ImYC9L>#?o#D}d5%k!i+=EX?G4u$uXd}K&pG#X z-dU#u%hl9-Jv~4~8*^jS`M0vWZ6rPWZzNtX49;&G+GkdR_(pX=LuAaDQjg4Nl2q!E zaVR_{@!-1Trtk?~ajC8ejsnnXYj$Q;$U^ zDB{vihNramFta4%R%QCqDU*^U+Fp|SA(!jlyY?U@er3Q1qjL)TJM>)jX=S0ujq3a_ z=FLlriCZ?R@Pp9-*+GS5;%Vpr$xHgFEZ5luzOS0OR5WK1Lb-_~K83-B3vjRg+5^ML zo!uwjF8^3H`v@uRm3NF{Km3+@?I&xldJa(qsU$hW75WtQdW(ZgkDOT4wDr)MgcSvc z4k|}2JN5PYWV+PSqF6o2MwELrdLHQ+IXw5B^#~{=lXN>Z@Ki}dUOHTEEgv-Ff}#si zW%?Ez+q>-31E1%A5j@#q#*bHD+Uxhmi#Ez6vPr&z3q3h)*U&evUDX}^x?`%vm<-JVx>TXtEFW`O*xYn)%y;k_Xwz0}aqC00Rkw^;Z?&_+d#`8b%!I^XU;LNjF zaOTmM?%7x4;yu4P{pv+dKV)fW|NeyBqeKd~65(|ucF2TXcClA$Wr9Q1knhrIr-itP zK8@{8Nh5ZrHs8k3<%^~e@?n{Q56{fyTMVN3f&>$jDoZxGQy%+x*Jaxy(@DKdg+t=}0~(GenVZ zo7B|f1?pr^NU@gHsB@y3QH8Xx1H$;08N%yH02~P*GObmuW4P3jI>Te@+0}l8w51-@ zk#^$(hSA)22zkG0ifo&H7kt|9zK$YrwPA;$oRTqa1gp7Y+;j3* zff<#Ui-HwGb)gPIA&qK1lwO zJVic3zEHka{_Fi>QE^iM#d#|rTK&if5)NlbHECo_5s60_cTP)!usKX6!;?tHPbBmX z8C4`2nQ@7XCYd)$m_!jX`hh6vCo=2~$+$xhGpoe-ZV#5fc zz27H2+9|A~0u*5KmESYj8%&mCXLHY>kP{YCHc0-We4Ko`{9XAP`R?mAmh^Hx*t}=( z=ZKNbd!(67=5~W;b0=wGb8nHTh0U!Jg^A4_caYD>=03u+>1U~k+(`mRppSy_*`b{x zD(DaRv}cV89pJHireCiS{d&Kx^f0yjia10?iWs3md!a=IMjzAbJfqO|9?NJQ80EWN41W2Ku0ywl|2tkO+t&mb)Kv{Fr#|kLlp4cbWJ)9%{0j+Y z4QlH4$BZMMP56TOb2g#a{5KkOXd#Q%Ng$#A9~npdA2+BOMp8`$qL;FHUh)E)v3)RqxCFCc+c9(IPi!x}Bm4wXL;x0S4ID=k@Cyg83W!_6O=!{i0 zglu(}<>h72k}e+*vdLXm^v$CPCs=NZe%k4?GrpxvGBKPV}PFUn>>gQsS zyVO`wZXphllPz#A3#ap?RVXb$^{OcPHCQL5ezHZ@QnT=Uv&;2a&|jg}Vt!)*;6*I~ z;D4e&(U&ZkQY!?2*W5S2$PmP}rSCoP7dz^?piKpI({G!loL;c~4XEF*8(_=Fg13i# zGG8}T6K`?tL3y~^I>|no4+L*LK%){{FOnazzhHmMJ{-X$-R<4;oKK0JtZ!sVk>2w=u0Zm6eC=tl_KsP?Tu<_(<(=~YNMkxXq%%nORo6h zSvU$dw}B_TMG;Q1gvN>VA=$ix=sh~~=NrFm-$!th|I*p6eJloM#6)=h8s@Y4J^M+z zkz!e_%ds}?g5QpC@{*j^ZPe}d$$7?55UYTzJH(}bS!$5V<*EsicGu`-iFw%FE?fDS z!pb(P@cZS`>*po$qAjl?M30z6OW_f*DL&v!SM-qY)WF+Q2O91hx)+P$l>UWS4O~g; zI(>Q}R)Z)q>J&-ZN5=2cXOJERex!yp*%VW8oXG#u4m}|`5wB{StRjnP+q&|f{pFVj zDK7g#bsl4GQ>SYa);Y{0G>Zmg(Rk6WSIvv)Nd3*6zzK=Il(b*rZ#Hz7emElAZ}v;6 z2~2BaV<}fU7n>pS!v`_0G4U4C&DS1`j&Pb9_=%W$5RYLwfwbWleWw-v7HfUqn^5kN z^szp3v)|P~ntRz#kzlDwb4&OsCSK~7b1ODZDjxTXUu{LVvA_6PlKVkL5eAo}v?*!x z(^jUHI=+$gz}zake%X*& zX7IjOb-P?rT18E=WfU#_*=r9(_E3wh$_$59*&$GgP)wO&hJSa_9fS@D>`g@QaYw~P zRcs%rVYNK;VGOX39?9D;iVi~V!T1gfEI!g;#6c_KByx@1t?i#>hb5R)`A5-1$Zn1A zLUxv81XA;dRVFe1hXsirppT*x*AVgp>^ z#s}cvTu@H6P(a{6F`oS3$7adxW~FUM1oJ53Fs|9ij$xtUORK^h&EY>TKE{{ctgI-H zk#pQo>7sE6DE}-Q-j3!J07!mlOJ>O5vbmAeE+RMyuReU?& zytALHD55(pv}`Z!E((55qIh2JMqZSURm<*GO>6&&JbaOSoqQL}tZ>H{s1Z3t)XB}= zJGggqCyk@w02__qOE~6j5m5q2iI&6=nDR!Ek~ES?k~l*s85>o^l{9cZ;#)d*X${rI zdBd@dOd=IUB2X)3g-B6I6yaXO)+qr4XKiW`r;iQ^u0<)rKrGp@=I`$%~sxrjhtELk`mkI7+qHg1c$i3m{+f3$Tpggu}b? zOdScSH6WI~28~uS?INF>&&;vhEVJFCwT2QdifB>BAa>UylO&_0@=--x2Phe=egufm zG1d>&P^kZ*24wXIkZhJSi;OSgrt@zrFkauB5!)6cF9QZS{Q@8uy~UgZ8gqI zNJfnzzKH95rA{A5&?$XI*B+RO&1KH!tZNvv!vv9;02%oNPYTV=5) zah2byFg_wkLP-)HNvt)z#xu#yu85n%7BUP0E^eCp8$Zzbao)%ebdgLfwa1(zM)l0VpkT|?BcDGuJYY+RnLdgt zam2ya20y|m(?J;q3Y*IXN);%%pt!V9QfU<30t!w_jL%#Lxtzfnv&a7?jhX^lo`(fFz2VZF;zzU7!Sj(x>H_Zqg_^9h5FDlzdRu zgW}ji=_re$M?i6Eq2z&b6BJYJZOo#m!{&!`<$~f5is_jKP^N?8+EPxYc@$j-imCQH zpnMC8=}i+YqG+2fgbZxSRRqclpqSnw+A@mH0%Z{Y4E}-M$^MrGVZy3|@6{M}SSO<~ zlV&GjZcKyi#b6}D-#y7@Ggf!v38EwaBC^8a- zOo6%FlNH~N&lCbuHjeu+_eIti5K_hi<#phH!k&+mut7e)-Y^uUG-CH|{E3aT8o1!7 zHXzszu^-R!BcF!PHw>>Z1M_DZ2BZgN6<5OuhgV^_8Z&?6Uui6{o$#9h5yO&@5B59b z&wMOd7GQh^mS8qB;{c2mU@E!MSaNSdO{}t#`K(c_wc^o&c+pl2HD~i6)C)fdFW+2I z65bS34NZS+mcBwwzh?nuvDSa=4?Q3IgXg0~HMf(e&`!TE3ON|RkNx5Cb$C5mNAs)W zht}*iSMK#kl_SUF>*)1Ib>N5Abu?FwA0Gc6_~G$&^bzWSeh1kIKv-^W0_1_6RbLoI zw3J}3oIq@oD9JPd$UlZAYqf1O6(AXqtP(2 zC36(3rTIsG0Hrsg5}Ju8gfZ6YiEJ%g_G6La4EAi-#3Wf5vuvDUan1xUj58aoZm_C@ zdoBZZha@spC=wij&O{cA*F@0$Nw3seLx$8iJX`-5O{`z5XG0J#%Kwr@8WXAw$tZRr z!8#vJUZYqN7^CKj5B8P#;yrnKc)yf z6Jh2A_<_BxP`z=+;iHX_;*@Hv+vE8c#ms_I5aEK<_>x4vk%)B$n|+OK9EA$F1ST)w zJ)5;^21!Bnj>gwYB-u>JCy>GDH(rEbZAZV2;>AK}YxY9CphCb%b{HOjxN`=xOv3t0 z=*v)OHP!w#hQvnJQQ(2k@66Z2Cceh;wM<1oRk4|n`zSFgGSgyHCpS_6awt%mn%UP{8*c8r+{m`K_;Pr+y|jNkC){+NhgvEzxVHzYR8#iKuC z;6;-(CZ)zP-70ep*0ox!Ev<%St0%w%KP)kr>J1b4T7+TAy0%j$pBe8c&{zmLC}Id9 zN0NEh4@B8DzR^VPMBtl!*3W~ zJH3`o4`5g*o^8=`F>KgYsrmB)zOckTJ(1p4hR_c_B1D1jfjlK2l()N!uWr=QAzO3Br6F8KdiKKg4<#Gn)EGV5@o(TZO(CV2)P%O3+(xK&y}A2FkOh7DHbYfuc20kgEX{ zQ=e|bGwpXi++XKF=?@Cq>atQBDU?I=0jmf?PhJXj>?mpbP`WxrHJ*7fD|S z#Ux{rJ;8K2DBWA0amF)8T0OH0dsu$D!2s$2iiw};c*X@3jVTx2qCY5ZEtFjFGYS-wj19nh z<+PN82YL00t<-F#Wu>##PKLyGDCS5ONR~)eNNtd;kyyXUkrYTuBo&exiM;`ndB&3+ z5|ef|`!I=S{m9ym@xt1NwVD=*Ne*irCnVNuY!+s1(*=pW2^*cyA+f&chQy??2a-FI z2a+d}7gA3oZzQH}`yespn5m6SDeZ^UA87#6^GE}cbVzz6UnD;yf207UK%^j~K}c+e zHyDY1V&47rK0Beo0Zo}91_%EzQ@1HISmQr!%M38YD9Q}5R>xp@R0FtCX22Ov47Nsg zWd>|*+T%Z1;hrfoU|k2yAO@RJ2fz`^3_y9r3`B5uSfQ{vLpDFGp+NNstDev&ErxSE zENa4nDxAx3j(Wj%?l0BDb`E1HZ#?IY+XNyY0=5C^D5|?CdYm7PncSOzXWv^Dw5Uqx zbk^Z$?q-|3h*?jnn#!B1@nVU|el+$JieP!Vi&nP*4EpEZq^aB&z!s|DfgCSTzJQj0 zm_Yn@KYA2j0NZ>H7z@Mq$p3D2|BKS~$di8|j9vtXOb7)ITOB^M`VSj0|9m?#5zj?C z>Ixn{;{frA`7jO}e4}VcD-Nd}hK2yC+g)i`b)&kbk`h^&!BQ&0t{Y==Or^oGi1U^k z*pYZiL(K!?)sd|3#?DQXVF7mI&IBKr@-N^Uv)zJ!+~Xw=*-lOkKG55JmuOSl-Cu0~ zthgz0NX>l&x7O^3-W(J_bIJ^#4rPjsUADB+puJUbyN&HITMfcDWO8A5Ywk}ZJ<1HZ znNekQZl%G8?bSOVXUI=s^kAhS$g7T=&67{IC!OxmmlP#OmJ>3&47SP{&ZSks?_(~U zYX8Em3Ww6FSq~TYjPAlx*EfF>oQdrOfHRKbSce0st8kg*3(;x@!`KIep8IH zFEjj9X4uwCQS$8;LQYqr+ONFS6bDeb=gIWDme2t(mvs9c*nZ_cOr2dE1KM1t{@Rxc Qf?eX-Wc&2