/*
 * 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.
 */

/* $Id: PDFCIDFont.java 1758773 2016-09-01 13:02:29Z ssteiner $ */

package org.apache.fop.pdf;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Set;

import org.apache.fop.fonts.CIDFontType;

// based on work by Takayuki Takeuchi

/**
 * Class representing a "character identifier" font (p 210 and onwards).
 */
public class PDFCIDFont extends PDFObject {

    private String basefont;
    private CIDFontType cidtype;
    private Integer dw;
    private PDFWArray w;
    private int[] dw2;
    private PDFWArray w2;
    private PDFCIDSystemInfo systemInfo;
    private PDFCIDFontDescriptor descriptor;
    private PDFCMap cmap;

    /**
     * /CIDToGIDMap (only for CIDFontType2, see p 212)
     * can be either "Identity" (default) or a PDFStream
     */
    private PDFStream cidMap;


    /**
     * Create the /Font object
     * @param basefont Name of the basefont
     * @param cidtype CID type
     * @param dw default width
     * @param w array of character widths
     * @param registry name of the issuer
     * @param ordering Unique name of the font
     * @param supplement Supplement number
     * @param descriptor CID font descriptor
     */
    public PDFCIDFont(String basefont, CIDFontType cidtype, int dw,
            int[] w, String registry, String ordering,
            int supplement, PDFCIDFontDescriptor descriptor) {

        this(basefont, cidtype, dw,
                new PDFWArray(w),
                new PDFCIDSystemInfo(registry, ordering, supplement),
                descriptor);
    }

    /**
     * Create the /Font object
     * @param basefont Name of the basefont
     * @param cidtype CID type
     * @param dw default width
     * @param w array of character widths
     * @param systemInfo CID system info
     * @param descriptor CID font descriptor
     */
    public PDFCIDFont(String basefont, CIDFontType cidtype, int dw,
                      int[] w, PDFCIDSystemInfo systemInfo,
                      PDFCIDFontDescriptor descriptor) {

        this(basefont, cidtype, dw,
                new PDFWArray(w),
                systemInfo,
                descriptor);
    }

    /**
     * Create the /Font object
     * @param basefont Name of the basefont
     * @param cidtype CID type
     * @param dw default width
     * @param w array of character widths
     * @param systemInfo CID system info
     * @param descriptor CID font descriptor
     */
    public PDFCIDFont(String basefont, CIDFontType cidtype, int dw,
                      PDFWArray w, PDFCIDSystemInfo systemInfo,
                      PDFCIDFontDescriptor descriptor) {

        super();

        this.basefont = basefont;
        this.cidtype = cidtype;
        this.dw = dw;
        this.w = w;
        this.dw2 = null;
        this.w2 = null;
        systemInfo.setParent(this);
        this.systemInfo = systemInfo;
        this.descriptor = descriptor;
        this.cidMap = null;
        this.cmap = null;
    }

    /**
     * Set the /DW attribute
     * @param dw the default width
     */
    public void setDW(int dw) {
        this.dw = dw;
    }

    /**
     * Set the /W array
     * @param w the width array
     */
    public void setW(PDFWArray w) {
        this.w = w;
    }

    /**
     * Set the (two elements) /DW2 array
     * @param dw2 the default metrics for vertical writing
     */
    public void setDW2(int[] dw2) {
        this.dw2 = dw2;
    }

    /**
     * Set the two elements of the /DW2 array
     * @param posY position vector
     * @param displacementY displacement vector
     */
    public void setDW2(int posY, int displacementY) {
        this.dw2 = new int[] {
            posY, displacementY
        };
    }

    /**
     * Set the CMap used as /ToUnicode cmap
     * @param cmap character map
     */
    public void setCMAP(PDFCMap cmap) {
        this.cmap = cmap;
    }

    /**
     * Set the /W2 array
     * @param w2 array of metrics for vertical writing
     */
    public void setW2(PDFWArray w2) {
        this.w2 = w2;
    }

    /**
     * Set the /CIDToGIDMap (to be used only for CIDFontType2)
     * @param map mapping information
     */
    public void setCIDMap(PDFStream map) {
        this.cidMap = map;
    }

    /**
     * Set the /CIDToGIDMap (to be used only for CIDFontType2) to "Identity"
     */
    public void setCIDMapIdentity() {
        this.cidMap = null;    // not an error here, simply use the default
    }

    /**
     * Returns the PDF name for a certain CID font type.
     * @param cidFontType CID font type
     * @return corresponding PDF name
     */
    protected String getPDFNameForCIDFontType(CIDFontType cidFontType) {
        if (cidFontType == CIDFontType.CIDTYPE0) {
            return cidFontType.getName();
        } else if (cidFontType == CIDFontType.CIDTYPE2) {
            return cidFontType.getName();
        } else {
            throw new IllegalArgumentException("Unsupported CID font type: "
                        + cidFontType.getName());
        }
    }

    /**
     * {@inheritDoc}
     */
    public String toPDFString() {
        StringBuffer p = new StringBuffer(128);
        p.append("<< /Type /Font");
        p.append("\n/BaseFont /");
        p.append(this.basefont);
        p.append(" \n/CIDToGIDMap ");
        if (cidMap != null) {
            p.append(cidMap.referencePDF());
        } else {
            p.append("/Identity");
            //This is the default. We still write it because PDF/A requires it.
        }
        p.append(" \n/Subtype /");
        p.append(getPDFNameForCIDFontType(this.cidtype));
        p.append("\n");
        p.append(systemInfo.toPDFString());
        p.append("\n/FontDescriptor ");
        p.append(this.descriptor.referencePDF());

        if (cmap != null) {
            p.append("\n/ToUnicode ");
            p.append(cmap.referencePDF());
        }
        if (dw != null) {
            p.append("\n/DW ");
            p.append(this.dw);
        }
        if (w != null) {
            p.append("\n/W ");
            p.append(w.toPDFString());
        }
        if (dw2 != null) {
            p.append("\n/DW2 [");    // always two values, see p 211
            p.append(this.dw2[0]);
            p.append(this.dw2[1]);
            p.append("]");
        }
        if (w2 != null) {
            p.append("\n/W2 ");
            p.append(w2.toPDFString());
        }
        p.append("\n>>");
        return p.toString();
    }

    /**
     * {@inheritDoc}
     */
    public byte[] toPDF() {
        ByteArrayOutputStream bout = new ByteArrayOutputStream(128);
        try {
            bout.write(encode("<< /Type /Font\n"));
            bout.write(encode("/BaseFont /"));
            bout.write(encode(this.basefont));
            bout.write(encode(" \n"));
            bout.write(encode("/CIDToGIDMap "));
            bout.write(encode(cidMap != null ? cidMap.referencePDF() : "/Identity"));
            bout.write(encode(" \n"));
            bout.write(encode("/Subtype /"));
            bout.write(encode(getPDFNameForCIDFontType(this.cidtype)));
            bout.write(encode("\n"));
            bout.write(encode("/CIDSystemInfo "));
            bout.write(systemInfo.toPDF());
            bout.write(encode("\n"));
            bout.write(encode("/FontDescriptor "));
            bout.write(encode(this.descriptor.referencePDF()));
            bout.write(encode("\n"));
            if (cmap != null) {
                bout.write(encode("/ToUnicode "));
                bout.write(encode(cmap.referencePDF()));
                bout.write(encode("\n"));
            }
            if (dw != null) {
                bout.write(encode("/DW "));
                bout.write(encode(this.dw.toString()));
                bout.write(encode("\n"));
            }
            if (w != null) {
                bout.write(encode("/W "));
                bout.write(encode(w.toPDFString()));
                bout.write(encode("\n"));
            }
            if (dw2 != null) {
                bout.write(encode("/DW2 [")); // always two values, see p 211
                bout.write(encode(Integer.toString(this.dw2[0])));
                bout.write(encode(Integer.toString(this.dw2[1])));
                bout.write(encode("]\n"));
            }
            if (w2 != null) {
                bout.write(encode("/W2 "));
                bout.write(encode(w2.toPDFString()));
                bout.write(encode("\n"));
            }
            bout.write(encode(">>"));
        } catch (IOException ioe) {
            log.error("Ignored I/O exception", ioe);
        }
        return bout.toByteArray();
    }

    @Override
    public void getChildren(Set<PDFObject> children) {
        super.getChildren(children);
        if (cidMap != null) {
            children.add(cidMap);
            cidMap.getChildren(children);
        }
        children.add(descriptor);
        descriptor.getChildren(children);
        if (cmap != null) {
            children.add(cmap);
            cmap.getChildren(children);
        }
    }

}

