All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jpedal.objects.acroforms.creation.AnnotationFactory Maven / Gradle / Ivy

There is a newer version: 20151002
Show newest version
/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/support/
 *
 * (C) Copyright 1997-2017 IDRsolutions and Contributors.
 *
 * This file is part of JPedal/JPDF2HTML5
 *
     This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * AnnotationFactory.java
 * ---------------
 */
package org.jpedal.objects.acroforms.creation;

import java.awt.*;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Calendar;
import javax.imageio.ImageIO;
import org.jpedal.color.DeviceCMYKColorSpace;
import org.jpedal.fonts.FontMappings;
import org.jpedal.fonts.StandardFonts;
import org.jpedal.objects.GraphicsState;
import static org.jpedal.objects.acroforms.creation.SwingFormFactory.curveInk;
import org.jpedal.objects.raw.FormObject;
import org.jpedal.objects.raw.PdfArrayIterator;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.render.DynamicVectorRenderer;
import org.jpedal.utils.LogWriter;

public class AnnotationFactory {

    /**
     * Determine the type of annotation from the sub type value and call
     * appropriate method to create an icon for the annotation
     *
     * @param form PdfObject containing the annotation
     * @return BufferedImage of the annotation or null if the supplied FormObject contains errors
     */
    public static BufferedImage getIcon(final PdfObject form){
        return getIcon(form, 1.0f);
    }
    
    /**
     * Determine the type of annotation from the sub type value and call
     * appropriate method to create an icon for the annotation with the given
     * scaling applied
     *
     * @param form PdfObject containing the annotation
     * @param scaling Scaling to display the annotation at
     * @return BufferedImage of the annotation or null if the supplied FormObject contains errors
     */
    public static BufferedImage getIcon(final PdfObject form, final float scaling){
        
        switch(form.getParameterConstant(PdfDictionary.Subtype)){
            case PdfDictionary.Text :
                return getTextIcon(form);
            case PdfDictionary.Highlight :
                return getHightlightIcon(form, scaling);
            case PdfDictionary.Square :
                return getSquareIcon(form, scaling);
            case PdfDictionary.Underline :
                return getUnderLineIcon(form);
            case PdfDictionary.StrickOut :
                return getStrickOutIcon(form);
            case PdfDictionary.Caret :
                return getCaretIcon(form);
            case PdfDictionary.FileAttachment :
                return getFileAttachmentIcon();
            case PdfDictionary.Line :
                return getLineIcon(form, scaling);
            case PdfDictionary.Polygon :
                return getPolyIcon(form, false, scaling);
            case PdfDictionary.PolyLine :
                return getPolyIcon(form, true, scaling);
            case PdfDictionary.Circle :
                return getCircleIcon(form, scaling);
            case PdfDictionary.Squiggly:
                return getSquigglyIcon(form);
            case PdfDictionary.Sound:
                return getSoundIcon(form);
            case PdfDictionary.Ink:
                return getInkIcon(form, scaling);
        }
        
        return null;
    }
    
    private static BufferedImage getInkIcon(final PdfObject form, final float scaling){
        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        if (quad != null) {

            final Rectangle bounds = getFormBounds((FormObject) form, quad, scaling);
            final Object[] InkListArray = form.getObjectArray(PdfDictionary.InkList);
            if(bounds.width>0 && bounds.height>0){
                final BufferedImage icon1 = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g2 = (Graphics2D)icon1.getGraphics();
                setStroke(form, g2, scaling);
                scanInkListTree(InkListArray, form, g2, scaling);
                return icon1;
            }
        }
        return null;
    }
    
    /**
     * Method to scan through an InkList array, draw the ink to the provided
     * Graphics object and return the bounds of the InkList
     *
     * @param InkListArray Object array representing an InkList
     * @param form FormObject for the Ink annotation this InkList came from
     * @param g Graphics object to draw the Ink to
     * @return float array representing the Inks bounds in the order 
     * lowest x / y, largest x / x
     */
    public static float[] scanInkListTree(final Object[] InkListArray, final PdfObject form, final Graphics g){
        return scanInkListTree(InkListArray, form, g, 1.0f);
    }
    
    /**
     * Method to scan through an InkList array, draw the ink to the provided
     * Graphics object at the specified scaling and return the bounds of the InkList
     *
     * @param InkListArray Object array representing an InkList
     * @param form FormObject representing the Ink annotation this InkList came from
     * @param g Graphics object to draw the Ink to
     * @param scaling float value representing scaling where 1 equals 100%
     * @return float array representing the Inks bounds in the order 
     * lowest x / y, largest x / x
     */
    public static float[] scanInkListTree(final Object[] InkListArray, final PdfObject form, final Graphics g, final float scaling) {

        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        if (quad == null) {
            return null;
        }

        final Rectangle bounds = getFormBounds((FormObject) form, quad, scaling);

        float minX = bounds.x;
        float minY = bounds.y;
        float maxX = bounds.x+bounds.width;
        float maxY = bounds.y+bounds.height;

        float[] vals = null;
        final Graphics2D g2 = (Graphics2D) g;

        setStroke(form, g2, scaling);
        
        //if specific DecodeParms for each filter, set othereise use global
        if(InkListArray !=null){

            final int count= InkListArray.length;

            float x;
            float y;
            
            //If Graphics not set, don't draw anything.
            if(g!=null){
                final float[] underlineColor = form.getFloatArray(PdfDictionary.C);
                Color c1 = new Color(0);
                if(underlineColor!=null){
                    switch(underlineColor.length){
                        case 0:
                            //Should not happen. Do nothing. Annotation is transparent
                            break;
                        case 1:
                            //DeviceGrey colorspace
                            c1 = new Color(underlineColor[0],underlineColor[0],underlineColor[0],1.0f);
                            break;
                        case 3:
                            //DeviceRGB colorspace
                            c1 = new Color(underlineColor[0],underlineColor[1],underlineColor[2],1.0f);
                            break;
                        case 4:
                            //DeviceCMYK colorspace
                            final DeviceCMYKColorSpace cmyk = new DeviceCMYKColorSpace();
                            cmyk.setColor(underlineColor, 4);
                            c1 = new Color(cmyk.getColor().getRGB());

                            break;
                        default:
                            break;
                    }
                }

                g2.setColor(new Color(0.0f,0.0f,0.0f,0.0f));
                g2.fillRect(0, 0, bounds.width, bounds.height);
                g2.setColor(c1);
                g2.setPaint(c1);
            }

            for(int i=0;i maxX) {
                                    maxX = v;
                                }
                                x = (v - bounds.x);
                                vals[i] = x;
                                break;
                            case 1:
                                if (v < minY) {
                                    minY = v;
                                }
                                if (v > maxY) {
                                    maxY = v;
                                }
                                y = bounds.height - (v - bounds.y);
                                vals[i] = y;

                                break;
                        }
                    }
                } else {
                    final float[] r = scanInkListTree((Object[]) InkListArray[i], form, g, scaling);
                    if (r[0] < minX) {
                        minX = r[0];
                    }
                    if (r[2] > maxX) {
                        maxX = r[2];
                    }
                    if (r[1] < minY) {
                        minY = r[1];
                    }
                    if (r[3] > maxY) {
                        maxY = r[3];
                    }
                }
            }
        }

            if (vals != null) {
                if (vals.length < 6) { //Only use lines on ink
                    if (g2 != null) {

                        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                for(int i=0; i rect[i]) {
                        bounds.x = (int) rect[i];
                    }
                    if (bounds.x + bounds.width < rect[i]) {
                        bounds.width = (int) (rect[i] - bounds.x);
                    }
                } else {
                    if (bounds.y > rect[i]) {
                        bounds.y = (int) rect[i];
                    }
                    if (bounds.y + bounds.height < rect[i]) {
                        bounds.height = (int) (rect[i] - bounds.y);
                    }
                }

            }
        }
        
        bounds.x *= scaling;
        bounds.y *= scaling;
        bounds.width *= scaling;
        bounds.height *= scaling;
        
        return bounds;
    }
    
    private static BufferedImage getStrickOutIcon(final PdfObject form){
        
        final Color color = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));

        float[] quad = form.getFloatArray(PdfDictionary.QuadPoints);
        if (quad == null) {
            quad = form.getFloatArray(PdfDictionary.Rect);
        }

        final Rectangle bounds = getFormBounds((FormObject)form, quad, 1.0f);
        if(bounds.width>0 && bounds.height>0){
            final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
            final Graphics g = icon.getGraphics();

            if (quad.length >= 8) {
                for (int hi = 0; hi != quad.length; hi += 8) {
                    final int x = (int) quad[hi] - bounds.x;
                    int y = (int) quad[hi + 5] - bounds.y;
                    //Adjust y for display
                    y = (bounds.height - y) - (int) (quad[hi + 1] - quad[hi + 5]);
                    final int width = (int) (quad[hi + 2] - quad[hi]);
                    final int height = (int) (quad[hi + 1] - quad[hi + 5]);

                    try {
                        g.setColor(new Color(0.0f, 0.0f, 0.0f, 0.0f));
                        g.fillRect(0, 0, width, height);
                        g.setColor(color);
                        g.fillRect(x, y + (height / 2), width, 1);
                    } catch (final Exception e) {
                        LogWriter.writeLog("Exception: " + e.getMessage());
                    }
                }
            }
            return icon;
        }
        return null;
    }
    
    private static BufferedImage getUnderLineIcon(final PdfObject form){
        
        final Color color = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        
        float[] quad = form.getFloatArray(PdfDictionary.QuadPoints);
        if (quad == null) {
            quad = form.getFloatArray(PdfDictionary.Rect);
        }
        
        final Rectangle bounds = getFormBounds((FormObject)form, quad, 1.0f);
        if(bounds.width>0 && bounds.height>0){
            final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
            final Graphics g = icon.getGraphics();

            if (quad.length >= 8) {
                for (int hi = 0; hi != quad.length; hi += 8) {
                    final int x = (int) quad[hi] - bounds.x;
                    int y = (int) quad[hi + 5] - bounds.y;
                    //Adjust y for display
                    y = (bounds.height - y) - (int) (quad[hi + 1] - quad[hi + 5]);
                    final int width = (int) (quad[hi + 2] - quad[hi]);
                    final int height = (int) (quad[hi + 1] - quad[hi + 5]);

                    try {
                        g.setColor(new Color(0.0f, 0.0f, 0.0f, 0.0f));
                        g.fillRect(x, y, width, height);
                        g.setColor(color);
                        g.fillRect(x, y + height - 1, width, 1);
                    } catch (final Exception e) {
                        LogWriter.writeLog("Exception: " + e.getMessage());
                    }
                }
            }

            return icon;
        }
        
        return null;
    }
    
    private static BufferedImage getSquigglyIcon(final PdfObject form){
        
        final Color color = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        
        float[] quad = form.getFloatArray(PdfDictionary.QuadPoints);
        if (quad == null) {
            quad = form.getFloatArray(PdfDictionary.Rect);
        }
        
        final Rectangle bounds = getFormBounds((FormObject)form, quad, 1.0f);
        if(bounds.width>0 && bounds.height>0){
            final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
            final Graphics g = icon.getGraphics();

            if (quad.length >= 8) {
                for (int hi = 0; hi != quad.length; hi += 8) {
                    final int x = (int) quad[hi] - bounds.x;
                    int y = (int) quad[hi + 5] - bounds.y;
                    //Adjust y for display
                    y = (bounds.height - y) - (int) (quad[hi + 1] - quad[hi + 5]);
                    final int width = (int) (quad[hi + 2] - quad[hi]);
                    final int height = (int) (quad[hi + 1] - quad[hi + 5]);
                    final int step = 6;
                    final int bottom = y + height-1;
                    final int top = bottom-(step/2);
                    try {
                        g.setColor(color);

                        for(int i=0; i0 && bounds.height>0){
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D)icon.getGraphics();
                final int width = setStroke(form, g, scaling);
                g.setColor(ic);
                g.fillRect(0, 0, bounds.width, bounds.height);
                g.setColor(c);
                g.drawRect(width/2, width/2, bounds.width-width, bounds.height-width);
    //            FormRenderUtilsG2.renderBorder(g, (FormObject)form, 0,0,icon.getWidth(), icon.getHeight());

                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }
    
    private static int setStroke(final PdfObject form, final Graphics2D g, final float scaling) {
        int borderWidth = 1;
        final PdfObject BS = form.getDictionary(PdfDictionary.BS);
        if (BS != null && g!=null) {
            final String s = BS.getName(PdfDictionary.S);
            borderWidth = BS.getInt(PdfDictionary.W);
            if (borderWidth == -1) {
                borderWidth = 1;
            }
            final PdfArrayIterator d = BS.getMixedArray(PdfDictionary.D);

            if (s == null || s.equals("S")) {
                borderWidth*=scaling;
                g.setStroke(new BasicStroke(borderWidth));
            } else {
                if (s.equals("D")) {
                    float[] dash = {3};
                    if (d != null && d.hasMoreTokens()) {
                        final int count = d.getTokenCount();
                        if (count > 0) {
                            dash = d.getNextValueAsFloatArray();
                        }
                    }
                    borderWidth*=scaling;
                    g.setStroke(new BasicStroke(borderWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, dash, 0));
                }
            }
        }
        return borderWidth;
    }
    
    private static BufferedImage getCircleIcon(final PdfObject form, final float scaling){
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        final Color ic = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.IC));
        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        
        if (quad != null) {
            
            final Rectangle bounds = getFormBounds((FormObject)form, quad, scaling);
            if(bounds.width>0 && bounds.height>0){
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D)icon.getGraphics();

                final int width = setStroke(form, g, scaling);

                g.setColor(ic);
                g.fillOval((width/2), (width/2), bounds.width-width, bounds.height-width);
                g.setColor(c);
                g.drawOval((width/2),(width/2), bounds.width-width, bounds.height-width);

                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }
    
    private static BufferedImage getLineIcon(final PdfObject form, final float scaling){
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        
        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        final float[] line = form.getFloatArray(PdfDictionary.L);
        if (quad != null && line != null) {
            
            final Rectangle bounds = getFormBounds((FormObject)form, quad, scaling);
            if(bounds.width>0 && bounds.height>0){
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D)icon.getGraphics();
                setStroke(form, g, scaling);
                g.setColor(c);
                g.drawLine((int)(line[0]*scaling)-bounds.x, (int)(bounds.height-((line[1]*scaling)-bounds.y)), (int)(line[2]*scaling)-bounds.x, (int)(bounds.height-((line[3]*scaling)-bounds.y)));

                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }
    
    private static BufferedImage getPolyIcon(final PdfObject form, final boolean line, final float scaling){
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        final Color ic = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.IC));
        
        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        final float[] vertices = form.getFloatArray(PdfDictionary.Vertices);
        
        if (quad != null && vertices!=null) {
            
            final Rectangle bounds = getFormBounds((FormObject)form, quad, scaling);
            if(bounds.width>0 && bounds.height>0){
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D)icon.getGraphics();
                setStroke(form, g, scaling);

                final GeneralPath gPath = new GeneralPath(Path2D.WIND_NON_ZERO);
                gPath.moveTo((int)(vertices[0]*scaling) - bounds.x, (int)(bounds.height - ((vertices[1]*scaling) - bounds.y)));

                for(int i=2; i!=vertices.length; i+=2){
                    gPath.lineTo((int)(vertices[i]*scaling) - bounds.x, (int)(bounds.height - ((vertices[i+1]*scaling) - bounds.y)));
                }

                if (!line) {
                    gPath.lineTo((int)(vertices[0]*scaling) - bounds.x, (int)(bounds.height - ((vertices[1]*scaling)-bounds.y)));
                    g.setColor(ic);
                    g.fill(gPath);
                }

                g.setColor(c);
                g.draw(gPath);

                return icon;
            }
        }

        //Return a small empty image as no highlight to make.
        return null;
    }
    
    private static BufferedImage getCaretIcon(final PdfObject form){
        final Color c = convertFloatArrayToColor(form.getFloatArray(PdfDictionary.C));
        final float[] rd = form.getFloatArray(PdfDictionary.RD);
        final float[] quad = form.getFloatArray(PdfDictionary.Rect);
        
        if (quad != null) {
            
            final Rectangle bounds = getFormBounds((FormObject)form, quad, 1.0f);
            if(bounds.width>0 && bounds.height>0){
                final BufferedImage icon = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics2D g = (Graphics2D)icon.getGraphics();
                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
                g.setStroke(new BasicStroke(rd[1]));
                g.setColor(c);
                g.drawLine(0, bounds.height, bounds.width/2, 0);
                g.drawLine(bounds.width/2, 0, bounds.width, bounds.height);

                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }
    
    private static BufferedImage getHightlightIcon(final PdfObject form, final float scaling){
        final float[] f = form.getFloatArray(PdfDictionary.C);
        Color c = new Color(0);
        if (f != null) {
            switch (f.length) {
                case 0:
                    //Should not happen. Do nothing. Annotation is transparent
                    break;
                case 1:
                    //DeviceGrey colorspace
                    c = new Color(f[0], f[0], f[0], 0.5f);
                    break;
                case 3:
                    //DeviceRGB colorspace
                    c = new Color(f[0], f[1], f[2], 0.5f);
                    break;
                case 4:
                    //DeviceCMYK colorspace
                    final DeviceCMYKColorSpace cmyk = new DeviceCMYKColorSpace();
                    cmyk.setColor(f, 4);
                    c = new Color(cmyk.getColor().getRGB());
                    c = new Color(c.getRed(), c.getGreen(), c.getBlue(), 0.5f);

                    break;
                default:
                    break;
            }
        }
        
        final float[] quad = form.getFloatArray(PdfDictionary.QuadPoints);
        if (quad != null) {
            final Rectangle bounds = ((FormObject)form).getBoundingRectangle();
            
            //Bounds is 0 so calculate based on quad areas
            if(bounds.getWidth()==0 && bounds.getHeight()==0){
                for(int i=0; i!=quad.length; i++){
                    if(i%2==0){
                        if(bounds.x>quad[i]){
                            bounds.x = (int)quad[i];
                        }
                        if(bounds.x+bounds.widthquad[i]){
                            bounds.y = (int)quad[i];
                        }
                        if(bounds.y+bounds.height0 && scaledHeight*scaling>0){
                final BufferedImage icon = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_4BYTE_ABGR);
                final Graphics g = icon.getGraphics();

                if (quad.length >= 8) {
                    for (int hi = 0; hi != quad.length; hi += 8) {
                        final int x = (int) quad[hi] - bounds.x;
                        int y = (int) quad[hi + 5] - bounds.y;
                        //Adjust y for display
                        y = (bounds.height - y) - (int) (quad[hi + 1] - quad[hi + 5]);
                        final int width = (int) (quad[hi + 2] - quad[hi]);
                        final int height = (int) (quad[hi + 1] - quad[hi + 5]);
                        final Rectangle rh = new Rectangle((int)(x*scaling), (int)(y*scaling), (int)(width*scaling), (int)(height*scaling));
                        g.setColor(c);
                        g.fillRect(rh.x, rh.y, rh.width, rh.height);
                    }
                }
                return icon;
            }
        }
        //Return a small empty image as no highlight to make.
        return null;
    }
    
    /**
     * Get the Icon to be used for Text annotations
     * 
     * @param form PdfObject representing a TextAnnotation
     * @return BufferedImage representing the icon for this annotation
     */
    public static BufferedImage getTextIcon(final PdfObject form){
        
        final String iconFile = getPngImageForAnnotation(form);
        
        BufferedImage commentIcon = null;
        try {
            commentIcon = ImageIO.read(AnnotationFactory.class.getResource(iconFile));
        } catch (final IOException e){
            LogWriter.writeLog("Exception: " + e.getMessage());
        }
        
        setColorForAnnotation(form, commentIcon);

        return commentIcon;
    }
    
    private static final int STYLE_KEY_FONT = 1718578804;
    private static final int STYLE_KEY_TEXT = 1952807028;
    private static final int STYLE_KEY_COLOR = 1668246639;
    
    /**
     * Load the font style described in a DS string into a Component for use by
     * some annotations (e.g FreeText annotations).
     * 
     * @param DS byte[] representing the DS string
     * @param textInput Component to be used by a given annotation
     * @param scaling float value representing scaling where 1 is equal to 100%
     */
    public static void loadFontValues(final byte[] DS, final Component textInput, final float scaling){
        
        Font font = new Font("Lucida", Font.PLAIN, 12);
        int position = 0;
        
        while(position='0' && DS[position]<='9')){ //ignore decimal as we use integer in java
                                size.append((char)DS[position]);
                                position++;
                            }
                            
                            //Progress to end
                            while(position0){
            date += '+';
        }
        if(offset!=0){
            date+=String.format("%02d", ((offset/1000)/3600));
            date+='\'';
            date+=String.format("%02d", ((offset/1000)%3600)/60);
            date+='\'';
        }
        return date;
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy