package com.lowagie.text.pdf;

import java.io.UnsupportedEncodingException;
import java.nio.IntBuffer;
import java.util.List;
import java.util.Map;
import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
import org.apache.fop.complexscripts.util.CharAssociation;
import org.apache.fop.complexscripts.util.CharScript;
import org.apache.fop.complexscripts.util.GlyphSequence;
import org.apache.fop.fonts.truetype.TTFFile;

/**
 * A patched copy of OpenPDF's {@code FopGlyphProcessor}, shadowing the one in
 * openpdf 2.0.3.
 *
 * <p><strong>Why this file exists.</strong> The shipped version crashes with an
 * {@code IndexOutOfBoundsException} on Devanagari text whose glyph count grows
 * during substitution. It builds {@code charBuffer} with one entry per
 * <em>character</em>, then reads it back with the index of each output
 * <em>glyph</em>:
 *
 * <pre>
 *     int limit = glyphSequence.getGlyphs().limit();   // glyphs, after GSUB
 *     for (int i = 0; i &lt; limit; i++) {
 *         ... charBuffer.get(i) ...                    // characters, before GSUB
 *     }
 * </pre>
 *
 * <p>For most words the two counts happen to match, or substitution reduces the
 * glyph count and the read stays in bounds — which is why conjuncts like
 * {@code ज्ञान} and {@code शक्ति} render. But Devanagari substitution can also
 * <em>increase</em> the count, and then the read runs off the end. The Hindi for
 * July, {@code जुलाई}, is one such word: five characters, more glyphs, and every
 * PDF containing it failed to render.
 *
 * <p>The fix below is upstream's, taken from openpdf 3.0.5
 * ({@code org.openpdf.text.pdf.FopGlyphProcessor}), where the class was
 * corrected after being deprecated in the {@code com.lowagie} package. It maps
 * each output glyph back to its source character through FOP's
 * {@link CharAssociation} list rather than assuming the indices line up — which
 * also gives the {@code /ToUnicode} map a truthful character for ligated glyphs,
 * so copying text out of a generated PDF works better than it did.
 *
 * <p>An extra bounds check guards the case where FOP returns no associations.
 *
 * <p>Delete this file when the project moves to openpdf 3.x, whose
 * {@code org.openpdf} package carries the fix. Application classes precede
 * dependency jars on the classpath, so this copy wins while it is here.
 *
 * @author Gajendra kumar (raaz2.gajendra@gmail.com) — original
 */
public class FopGlyphProcessor {

    private static boolean isFopSupported;

    static {
        try {
            Class.forName("org.apache.fop.complexscripts.util.GlyphSequence");
            isFopSupported = true;
        } catch (ClassNotFoundException e) {
            isFopSupported = false;
        }
    }

    public static boolean isFopSupported() {
        return isFopSupported;
    }

    public static byte[] convertToBytesWithGlyphs(BaseFont font, String text, String fileName,
            Map<Integer, int[]> longTag, String language) throws UnsupportedEncodingException {
        TrueTypeFontUnicode ttu = (TrueTypeFontUnicode) font;
        IntBuffer charBuffer = IntBuffer.allocate(text.length());
        IntBuffer glyphBuffer = IntBuffer.allocate(text.length());
        int textLength = text.length();
        for (char c : text.toCharArray()) {
            int[] metrics = ttu.getMetricsTT(c);
            // metrics will be null in case glyph not defined in TTF font, skip these characters.
            if (metrics == null) {
                textLength--;
                continue;
            }
            charBuffer.put(c);
            glyphBuffer.put(metrics[0]);
        }
        charBuffer.limit(textLength);
        glyphBuffer.limit(textLength);

        GlyphSequence glyphSequence = new GlyphSequence(charBuffer, glyphBuffer, null);
        TTFFile ttf = TTFCache.getTTFFile(fileName, ttu);
        GlyphSubstitutionTable gsubTable = ttf.getGSUB();
        if (gsubTable != null) {
            String script = CharScript.scriptTagFromCode(CharScript.dominantScript(text));
            if ("zyyy".equals(script) || "auto".equals(script)) {
                script = "*";
            }
            glyphSequence = gsubTable.substitute(glyphSequence, script, language);
        }
        int limit = glyphSequence.getGlyphs().limit();
        int[] processedChars = glyphSequence.getGlyphs().array();
        char[] charEncodedGlyphCodes = new char[limit];
        List<CharAssociation> associations = glyphSequence.getAssociations();

        for (int i = 0; i < limit; i++) {
            charEncodedGlyphCodes[i] = (char) processedChars[i];
            Integer glyphCode = processedChars[i];
            if (!longTag.containsKey(glyphCode)) {
                int sourceIdx = i;
                if (associations != null && i < associations.size()) {
                    sourceIdx = associations.get(i).getStart();
                }
                // Upstream trusts the association index. Clamp anyway: with no
                // associations at all this is still the glyph index, and running
                // off the end is the very fault this file exists to fix.
                int originalChar = sourceIdx < charBuffer.limit() ? charBuffer.get(sourceIdx) : 0;

                longTag.put(glyphCode, new int[]{
                        processedChars[i],
                        ttu.getGlyphWidth(processedChars[i]),
                        originalChar
                });
            }
        }
        return new String(charEncodedGlyphCodes).getBytes(CJKFont.CJK_ENCODING);
    }
}
