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}