001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wmps/DefaultGetMapHandler.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     ---------------------------------------------------------------------------*/
043    package org.deegree.ogcwebservices.wmps;
044    
045    import java.awt.Color;
046    import java.awt.Font;
047    import java.awt.Graphics;
048    import java.awt.Graphics2D;
049    import java.awt.RenderingHints;
050    import java.awt.image.BufferedImage;
051    import java.util.ArrayList;
052    import java.util.List;
053    
054    import org.deegree.framework.log.ILogger;
055    import org.deegree.framework.log.LoggerFactory;
056    import org.deegree.framework.util.CharsetUtils;
057    import org.deegree.framework.util.ImageUtils;
058    import org.deegree.framework.util.MapUtils;
059    import org.deegree.framework.util.StringTools;
060    import org.deegree.framework.xml.XMLParsingException;
061    import org.deegree.graphics.MapFactory;
062    import org.deegree.graphics.MapView;
063    import org.deegree.graphics.Theme;
064    import org.deegree.graphics.optimizers.LabelOptimizer;
065    import org.deegree.graphics.sld.AbstractLayer;
066    import org.deegree.graphics.sld.AbstractStyle;
067    import org.deegree.graphics.sld.NamedLayer;
068    import org.deegree.graphics.sld.NamedStyle;
069    import org.deegree.graphics.sld.SLDFactory;
070    import org.deegree.graphics.sld.StyledLayerDescriptor;
071    import org.deegree.graphics.sld.UserLayer;
072    import org.deegree.graphics.sld.UserStyle;
073    import org.deegree.i18n.Messages;
074    import org.deegree.model.crs.CRSFactory;
075    import org.deegree.model.crs.CoordinateSystem;
076    import org.deegree.model.crs.GeoTransformer;
077    import org.deegree.model.crs.IGeoTransformer;
078    import org.deegree.model.spatialschema.Envelope;
079    import org.deegree.model.spatialschema.Geometry;
080    import org.deegree.model.spatialschema.GeometryFactory;
081    import org.deegree.ogcbase.InvalidSRSException;
082    import org.deegree.ogcwebservices.OGCWebServiceException;
083    import org.deegree.ogcwebservices.wmps.configuration.WMPSConfiguration;
084    import org.deegree.ogcwebservices.wmps.configuration.WMPSDeegreeParams;
085    import org.deegree.ogcwebservices.wms.LayerNotDefinedException;
086    import org.deegree.ogcwebservices.wms.StyleNotDefinedException;
087    import org.deegree.ogcwebservices.wms.capabilities.Layer;
088    import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
089    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
090    import org.deegree.ogcwebservices.wms.operation.GetMap;
091    
092    /**
093     * This is a copy of the WMS package.
094     * 
095     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
096     * @author last edited by: $Author: apoth $
097     * 
098     * @version $Revision: 7177 $, $Date: 2007-05-16 16:20:27 +0200 (Mi, 16 Mai 2007) $
099     */
100    public class DefaultGetMapHandler implements GetMapHandler {
101    
102        private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class );
103    
104        protected GetMap request;
105    
106        private Object[] themes;
107    
108        protected double scale = 0;
109    
110        private int count = 0;
111    
112        protected CoordinateSystem reqCRS;
113    
114        private WMPSConfiguration configuration;
115    
116        private BufferedImage copyrightImg;
117    
118        private Graphics graph;
119    
120        /**
121         * Creates a new GetMapHandler object.
122         * 
123         * @param configuration
124         * @param request
125         *            request to perform
126         * @throws OGCWebServiceException
127         */
128        public DefaultGetMapHandler( WMPSConfiguration configuration, GetMap request ) throws OGCWebServiceException {
129            this.request = request;
130            this.configuration = configuration;
131    
132            try {
133                // get copyright image if possible
134                this.copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyright() );
135            } catch ( Exception e ) {
136            }
137    
138            try {
139                this.reqCRS = CRSFactory.create( this.request.getSrs() );
140            } catch ( Exception e ) {
141                throw new InvalidSRSException( "SRS: " + request.getSrs() + "is nor known by the deegree WMS" );
142            }
143    
144        }
145    
146        /**
147         * returns the configuration used by the handler
148         * 
149         * @return WMPSConfiguration
150         */
151        public WMPSConfiguration getConfiguration() {
152            return this.configuration;
153        }
154    
155        /**
156         * increases the counter variable that holds the number of services that has sent a response.
157         * All data are available if the counter value equals the number of requested layers.
158         */
159        protected synchronized void increaseCounter() {
160            this.count++;
161        }
162    
163        /**
164         * performs a GetMap request and retruns the result encapsulated within a <tt>GetMapResult</tt>
165         * object.
166         * <p>
167         * The method throws an WebServiceException that only shall be thrown if an fatal error occurs
168         * that makes it imposible to return a result. If something wents wrong performing the request
169         * (none fatal error) The exception shall be encapsulated within the response object to be
170         * returned to the client as requested (GetMap-Request EXCEPTION-Parameter).
171         * 
172         * @param g
173         * @throws OGCWebServiceException
174         */
175        public void performGetMap( Graphics g )
176                                throws OGCWebServiceException {
177    
178            this.graph = g;
179    
180            try {
181                CoordinateSystem crs = CRSFactory.create( this.request.getSrs() );
182                this.scale = MapUtils.calcScale( this.request.getWidth(), this.request.getHeight(),
183                                                 this.request.getBoundingBox(), crs, 1 );
184                LOG.logInfo( "OGC WMS scale: " + this.scale );
185            } catch ( Exception e ) {
186                LOG.logDebug( "-", e );
187                throw new OGCWebServiceException( "Couldn't calculate scale! " + e );
188            }
189    
190            StyledLayerDescriptor sld = null;
191            try {
192                sld = toSLD( this.request.getLayers(), this.request.getStyledLayerDescriptor() );
193            } catch ( XMLParsingException e1 ) {
194                // should never happen
195                e1.printStackTrace();
196            }
197    
198            AbstractLayer[] layers = sld.getLayers();
199            // get the number of themes assigned to the selected layers
200            // notice that there maybe more themes as there are layers because
201            // 1 .. n datasources can be assigned to one layer.
202            int cntTh = countNumberOfThemes( layers, this.scale );
203            this.themes = new Object[cntTh];
204            // invokes the data supplyer for each layer in an independent thread
205            int kk = 0;
206            for ( int i = 0; i < layers.length; i++ ) {
207                if ( layers[i] instanceof NamedLayer ) {
208                    String styleName = null;
209                    if ( i < this.request.getLayers().length ) {
210                        styleName = this.request.getLayers()[i].getStyleName();
211                    }
212                    kk = invokeNamedLayer( layers[i], kk, styleName );
213                } else {
214                    GetMapServiceInvokerForUL si = new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], kk++ );
215    
216                    si.start();
217                }
218            }
219            waitForFinished();
220            renderMap();
221    
222        }
223    
224        /**
225         * Invoke the named layer
226         * 
227         * @param layer
228         * @param kk
229         * @param styleName
230         * @return int
231         * @throws OGCWebServiceException
232         */
233        private int invokeNamedLayer( AbstractLayer layer, int kk, String styleName )
234                                throws OGCWebServiceException {
235    
236            Layer lay = this.configuration.getLayer( layer.getName() );
237    
238            if ( validate( lay, layer.getName() ) ) {
239                UserStyle us = getStyles( (NamedLayer) layer, styleName );
240                AbstractDataSource[] ds = lay.getDataSource();
241    
242                for ( int j = 0; j < ds.length; j++ ) {
243    
244                    ScaleHint scaleHint = ds[j].getScaleHint();
245                    if ( this.scale >= scaleHint.getMin() && this.scale < scaleHint.getMax()
246                         && isValidArea( ds[j].getValidArea() ) ) {
247                        GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, lay, ds[j], us, kk++ );
248                        si.start();
249                    }
250                }
251            } else {
252                // set theme to null if no data are available for the requested
253                // area and/or scale
254                this.themes[kk++] = null;
255                increaseCounter();
256            }
257            return kk;
258        }
259    
260        /**
261         * returns the number of <code>DataSource</code>s involved in a GetMap request
262         * 
263         * @param layers
264         * @param currentscale
265         * @return int
266         */
267        private int countNumberOfThemes( AbstractLayer[] layers, double currentscale ) {
268            int cnt = 0;
269            for ( int i = 0; i < layers.length; i++ ) {
270                if ( layers[i] instanceof NamedLayer ) {
271                    Layer lay = this.configuration.getLayer( layers[i].getName() );
272                    AbstractDataSource[] ds = lay.getDataSource();
273                    for ( int j = 0; j < ds.length; j++ ) {
274    
275                        ScaleHint scaleHint = ds[j].getScaleHint();
276                        if ( currentscale >= scaleHint.getMin() && currentscale < scaleHint.getMax()
277                             && isValidArea( ds[j].getValidArea() ) ) {
278    
279                            cnt++;
280                        }
281                    }
282                } else {
283                    cnt++;
284                }
285            }
286            return cnt;
287        }
288    
289        /**
290         * returns true if the requested boundingbox intersects with the valid area of a datasource
291         * 
292         * @param validArea
293         * @return boolean
294         */
295        private boolean isValidArea( Geometry validArea ) {
296    
297            if ( validArea != null ) {
298                try {
299                    Envelope env = this.request.getBoundingBox();
300                    Geometry geom = GeometryFactory.createSurface( env, this.reqCRS );
301                    if ( !this.reqCRS.getName().equals( validArea.getCoordinateSystem().getName() ) ) {
302                        // if requested CRS is not identical to the CRS of the valid area
303                        // a transformation must be performed before intersection can
304                        // be checked
305                        IGeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
306                        geom = gt.transform( geom );
307                    }
308                    return geom.intersects( validArea );
309                } catch ( Exception e ) {
310                    // should never happen
311                    LOG.logError( "could not validate WMS datasource area", e );
312                }
313            }
314            return true;
315        }
316    
317        /**
318         * runs a loop until all sub requestes (one for each layer) has been finished or the maximum
319         * time limit has been exceeded.
320         * 
321         * @throws OGCWebServiceException
322         */
323        private void waitForFinished()
324                                throws OGCWebServiceException {
325            if ( this.count < this.themes.length ) {
326                // waits until the requested layers are available as <tt>DisplayElements</tt>
327                // or the time limit has been reached.
328                // if count == themes.length then no request must be performed
329                long timeStamp = System.currentTimeMillis();
330                long lapse = 0;
331                long timeout = 1000 * ( this.configuration.getDeegreeParams().getRequestTimeLimit() - 1 );
332                do {
333                    try {
334                        Thread.sleep( 50 );
335                        lapse += 50;
336                    } catch ( InterruptedException e ) {
337                        throw new OGCWebServiceException( "GetMapHandler", "fatal exception waiting for "
338                                                                           + "GetMapHandler results" );
339                    }
340                } while ( this.count < this.themes.length && lapse < timeout );
341                if ( System.currentTimeMillis() - timeStamp >= timeout ) {
342                    throw new OGCWebServiceException( "Processing of the GetMap request " + "exceeds timelimit" );
343                }
344            }
345        }
346    
347        /**
348         * 
349         * @param layers
350         * @param inSLD
351         * @return StyledLayerDescriptor
352         * @throws XMLParsingException
353         */
354        private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD )
355                                throws XMLParsingException {
356            StyledLayerDescriptor sld = null;
357    
358            if ( layers != null && layers.length > 0 && inSLD == null ) {
359                // Adds the content from the LAYERS and STYLES attribute to the SLD
360                StringBuffer sb = new StringBuffer( 5000 );
361                sb.append( "<?xml version=\"1.0\" encoding=\"" + CharsetUtils.getSystemCharset() + "\"?>" );
362                sb.append( "<StyledLayerDescriptor version=\"1.0.0\" " );
363                sb.append( "xmlns='http://www.opengis.net/sld'>" );
364    
365                for ( int i = 0; i < layers.length; i++ ) {
366                    sb.append( "<NamedLayer>" );
367                    sb.append( "<Name>" + layers[i].getName() + "</Name>" );
368                    sb.append( "<NamedStyle><Name>" + layers[i].getStyleName() + "</Name></NamedStyle></NamedLayer>" );
369                }
370                sb.append( "</StyledLayerDescriptor>" );
371    
372                try {
373                    sld = SLDFactory.createSLD( sb.toString() );
374                } catch ( XMLParsingException e ) {
375                    throw new XMLParsingException( StringTools.stackTraceToString( e ) );
376                }
377            } else if ( layers != null && layers.length > 0 && inSLD != null ) {
378                // if layers not null and sld is not null then SLD layers just be
379                // considered if present in the layers list
380                List<String> list = new ArrayList<String>();
381                for ( int i = 0; i < layers.length; i++ ) {
382                    list.add( layers[i].getName() );
383                }
384    
385                List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 );
386                AbstractLayer[] al = inSLD.getLayers();
387                for ( int i = 0; i < al.length; i++ ) {
388                    if ( list.contains( al[i].getName() ) ) {
389                        newList.add( al[i] );
390                    }
391                }
392                al = new AbstractLayer[newList.size()];
393                sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() );
394            } else {
395                // if no layers are defined ...
396                sld = inSLD;
397            }
398    
399            return sld;
400        }
401    
402        /**
403         * returns the <tt>UserStyle</tt>s assigned to a named layer
404         * 
405         * @param sldLayer
406         *            layer to get the styles for
407         * @param styleName
408         *            requested stylename (from the KVP encoding)
409         * @return UserStyle
410         * @throws OGCWebServiceException
411         */
412        private UserStyle getStyles( NamedLayer sldLayer, String styleName )
413                                throws OGCWebServiceException {
414    
415            AbstractStyle[] styles = sldLayer.getStyles();
416            UserStyle us = null;
417    
418            // to avoid retrieving the layer again for each style
419            Layer layer = null;
420            layer = this.configuration.getLayer( sldLayer.getName() );
421            int i = 0;
422            while ( us == null && i < styles.length ) {
423                if ( styles[i] instanceof NamedStyle ) {
424                    // styles will be taken from the WMS's style repository
425                    us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer );
426                } else {
427                    // if the requested style fits the name of the defined style or
428                    // if the defined style is marked as default and the requested
429                    // style if 'default' the condition is true. This includes that
430                    // if more than one style with the same name or more than one
431                    // style is marked as default always the first will be choosen
432                    if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) )
433                         || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) {
434                        us = (UserStyle) styles[i];
435                    }
436                }
437                i++;
438            }
439            if ( us == null ) {
440                // this may happens if the SLD contains a named layer but not
441                // a style! yes this is valid according to SLD spec 1.0.0
442                us = getPredefinedStyle( styleName, sldLayer.getName(), layer );
443            }
444            return us;
445        }
446    
447        /**
448         * Returns a Predifined UserStyle
449         * 
450         * @param styleName
451         * @param layerName
452         * @param layer
453         * @return UserStyle
454         * @throws StyleNotDefinedException
455         */
456        private UserStyle getPredefinedStyle( String styleName, String layerName, Layer layer )
457                                throws StyleNotDefinedException {
458            UserStyle us = null;
459    
460            if ( "default".equals( styleName ) ) {
461                us = layer.getStyle( styleName );
462            }
463    
464            if ( us == null ) {
465                if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" )
466                     || styleName.equals( "default" ) ) {
467                    styleName = "default:" + layerName;
468                }
469            }
470    
471            us = layer.getStyle( styleName );
472            if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) {
473                String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer );
474                throw new StyleNotDefinedException( s );
475            }
476            return us;
477        }
478    
479        /**
480         * validates if the requested layer matches the conditions of the request if not a
481         * <tt>WebServiceException</tt> will be thrown. If the layer matches the request, but isn't
482         * able to deviever data for the requested area and/or scale false will be returned. If the
483         * layer matches the request and contains data for the requested area and/or scale true will be
484         * returned.
485         * 
486         * @param layer
487         *            layer as defined at the capabilities/configuration
488         * @param name
489         *            name of the layer (must be submitted seperatly because the layer parameter can be
490         *            <tt>null</tt>
491         * @return boolean
492         * @throws OGCWebServiceException
493         */
494        private boolean validate( Layer layer, String name )
495                                throws OGCWebServiceException {
496    
497            // check if layer is available
498            if ( layer == null ) {
499                throw new LayerNotDefinedException( "Layer: " + name + " is not known by the WMS" );
500            }
501    
502            if ( !layer.isSrsSupported( this.request.getSrs() ) ) {
503                throw new InvalidSRSException( "SRS: " + this.request.getSrs() + "is not known by layer: " + name );
504            }
505    
506            // check for valid coordinated reference system
507            String[] srs = layer.getSrs();
508            boolean tmp = false;
509            for ( int i = 0; i < srs.length; i++ ) {
510                if ( srs[i].equalsIgnoreCase( this.request.getSrs() ) ) {
511                    tmp = true;
512                    break;
513                }
514            }
515    
516            if ( !tmp ) {
517                throw new InvalidSRSException( "layer: " + name + " can't be " + "delievered in SRS: "
518                                               + this.request.getSrs() );
519            }
520    
521            // check bounding box
522            try {
523    
524                Envelope bbox = this.request.getBoundingBox();
525                Envelope layerBbox = layer.getLatLonBoundingBox();
526                if ( !this.request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
527                    // transform the bounding box of the request to EPSG:4326
528                    IGeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
529                    bbox = gt.transform( bbox, this.reqCRS );
530                }
531                if ( !bbox.intersects( layerBbox ) ) {
532                    return false;
533                }
534    
535            } catch ( Exception e ) {
536                LOG.logError( e.getMessage(), e );
537                throw new OGCWebServiceException( "couldn't compare bounding boxes\n" + e.toString() );
538            }
539    
540            return true;
541        }
542    
543        /**
544         * put a theme to the passed index of the themes array. The second param passed is a
545         * <tt>Theme</tt> or an exception
546         * 
547         * @param index
548         * @param o
549         */
550        protected synchronized void putTheme( int index, Object o ) {
551            this.themes[index] = o;
552        }
553    
554        /**
555         * renders the map from the <tt>DisplayElement</tt>s
556         */
557        private void renderMap() {
558    
559            // GetMapResult response = null;
560            OGCWebServiceException exce = null;
561    
562            ArrayList<Object> list = new ArrayList<Object>( 50 );
563            for ( int i = 0; i < this.themes.length; i++ ) {
564                if ( this.themes[i] instanceof Exception ) {
565                    exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", this.themes[i].toString() );
566                }
567                if ( this.themes[i] instanceof OGCWebServiceException ) {
568                    exce = (OGCWebServiceException) this.themes[i];
569                    break;
570                }
571                if ( this.themes[i] != null ) {
572                    list.add( this.themes[i] );
573                }
574            }
575    
576            if ( exce == null ) {
577                // only if no exception occured
578                try {
579                    Theme[] th = list.toArray( new Theme[list.size()] );
580                    MapView map = null;
581                    if ( th.length > 0 ) {
582                        map = MapFactory.createMapView( "deegree WMS", this.request.getBoundingBox(), this.reqCRS, th );
583                    }
584                    this.graph.setClip( 0, 0, this.request.getWidth(), this.request.getHeight() );
585                    if ( !this.request.getTransparency() ) {
586                        this.graph.setColor( this.request.getBGColor() );
587                        this.graph.fillRect( 0, 0, this.request.getWidth(), this.request.getHeight() );
588                    }
589                    if ( map != null ) {
590                        Theme[] allthemes = map.getAllThemes();
591                        map.addOptimizer( new LabelOptimizer( allthemes ) );
592                        // antialiasing must be switched of for gif output format
593                        // because the antialiasing may create more than 255 colors
594                        // in the map/image, even just a few colors are defined in
595                        // the styles
596                        if ( !this.configuration.getDeegreeParams().isAntiAliased() ) {
597                            ( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_ANTIALIASING,
598                                                                          RenderingHints.VALUE_ANTIALIAS_ON );
599                            ( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
600                                                                          RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
601                        }
602                        map.paint( this.graph );
603                    }
604                } catch ( Exception e ) {
605                    LOG.logError( e.getMessage(), e );
606                    exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() );
607                }
608            }
609    
610            // print a copyright note at the left lower corner of the map
611            printCopyright( this.graph, this.request.getHeight() );
612    
613        }
614    
615        /**
616         * prints a copyright note at left side of the map bottom. The copyright note will be extracted
617         * from the WMS capabilities/configuration
618         * 
619         * @param g
620         *            graphic context of the map
621         * @param heigth
622         *            height of the map in pixel
623         */
624        private void printCopyright( Graphics g, int heigth ) {
625    
626            WMPSDeegreeParams dp = this.configuration.getDeegreeParams();
627            String copyright = dp.getCopyright();
628            if ( this.copyrightImg != null ) {
629                g.drawImage( this.copyrightImg, 8, heigth - this.copyrightImg.getHeight() - 5, null );
630            } else {
631                if ( copyright != null ) {
632                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
633                    g.setColor( Color.BLACK );
634                    g.drawString( copyright, 8, heigth - 15 );
635                    g.drawString( copyright, 10, heigth - 15 );
636                    g.drawString( copyright, 8, heigth - 13 );
637                    g.drawString( copyright, 10, heigth - 13 );
638                    g.setColor( Color.WHITE );
639                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
640                    g.drawString( copyright, 9, heigth - 14 );
641                    // g.dispose();
642                }
643            }
644        }
645    }