    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.imaging.formats.png.chunks;
    018
    019import java.io.ByteArrayInputStream;
    020import java.io.IOException;
    021import java.nio.charset.StandardCharsets;
    022import java.util.zip.InflaterInputStream;
    023
    024import org.apache.commons.imaging.ImagingException;
    025import org.apache.commons.imaging.common.Allocator;
    026import org.apache.commons.imaging.common.BinaryFunctions;
    027import org.apache.commons.imaging.formats.png.AbstractPngText;
    028import org.apache.commons.imaging.formats.png.PngConstants;
    029import org.apache.commons.io.IOUtils;
    030
    031public final class PngChunkItxt extends AbstractPngTextChunk {
    032
    033    private final String keyword;
    034    private final String text;
    035
    036    /**
    037     * The language tag defined in [RFC-3066] indicates the human language used by the translated keyword and the text. Unlike the keyword, the language tag is
    038     * case-insensitive. It is an ISO 646.IRV:1991 [ISO 646] string consisting of hyphen-separated words of 1-8 alphanumeric characters each (for example cn,
    039     * en-uk, no-bok, x-klingon, x-KlInGoN). If the first word is two or three letters long, it is an ISO language code [ISO-639]. If the language tag is empty,
    040     * the language is unspecified.
    041     */
    042    private final String languageTag;
    043
    044    private final String translatedKeyword;
    045
    046    /**
    047     * Constructs a new instance.
    048     *
    049     * @param length    chunk length.
    050     * @param chunkType chunk type.
    051     * @param crc       CRC computed over the chunk type and chunk data (but not the length).
    052     * @param bytes     chunk data bytes.
    053     * @throws ImagingException Thrown on a parsing error.
    054     * @throws IOException Thrown on reading error.
    055     * @throws NullPointerException if {@code bytes} is null.
    056     */
    057    public PngChunkItxt(final int length, final int chunkType, final int crc, final byte[] bytes) throws ImagingException, IOException {
    058        super(length, chunkType, crc, bytes);
    059        int terminator = BinaryFunctions.indexOf0(bytes, "PNG iTXt chunk keyword is not terminated.");
    060
    061        keyword = new String(bytes, 0, terminator, StandardCharsets.ISO_8859_1);
    062        int index = terminator + 1;
    063
    064        final int compressionFlag = bytes[index++];
    065        if (compressionFlag != 0 && compressionFlag != 1) {
    066            throw new ImagingException("PNG iTXt chunk has invalid compression flag: " + compressionFlag);
    067        }
    068
    069        final boolean compressed = compressionFlag == 1;
    070
    071        final int compressionMethod = bytes[index++];
    072        if (compressed && compressionMethod != PngConstants.COMPRESSION_DEFLATE_INFLATE) {
    073            throw new ImagingException("PNG iTXt chunk has unexpected compression method: " + compressionMethod);
    074        }
    075
    076        terminator = BinaryFunctions.indexOf0(bytes, index, "PNG iTXt chunk language tag is not terminated.");
    077        languageTag = new String(bytes, index, terminator - index, StandardCharsets.ISO_8859_1);
    078        index = terminator + 1;
    079
    080        terminator = BinaryFunctions.indexOf0(bytes, index, "PNG iTXt chunk translated keyword is not terminated.");
    081        translatedKeyword = new String(bytes, index, terminator - index, StandardCharsets.UTF_8);
    082        index = terminator + 1;
    083
    084        if (compressed) {
    085            final int compressedTextLength = bytes.length - index;
    086
    087            final byte[] compressedText = Allocator.byteArray(compressedTextLength);
    088            System.arraycopy(bytes, index, compressedText, 0, compressedTextLength);
    089
    090            text = new String(IOUtils.toByteArray(new InflaterInputStream(new ByteArrayInputStream(compressedText))), StandardCharsets.UTF_8);
    091
    092        } else {
    093            text = new String(bytes, index, bytes.length - index, StandardCharsets.UTF_8);
    094        }
    095    }
    096
    097    @Override
    098    public AbstractPngText getContents() {
    099        return new AbstractPngText.Itxt(keyword, text, languageTag, translatedKeyword);
    100    }
    101
    102    /**
    103     * @return Gets the keyword.
    104     */
    105    @Override
    106    public String getKeyword() {
    107        return keyword;
    108    }
    109
    110    /**
    111     * @return Gets the text.
    112     */
    113    @Override
    114    public String getText() {
    115        return text;
    116    }
    117
    118    public String getTranslatedKeyword() {
    119        return translatedKeyword;
    120    }
    121}