/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_parse_utils.cpp#3 $ */ 
/* $DateTime: 2012/06/06 12:08:58 $ */
/* $Change: 833617 $ */
/* $Author: tknoll $ */

/*****************************************************************************/

#include "dng_parse_utils.h"

#include "dng_date_time.h"
#include "dng_globals.h"
#include "dng_ifd.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_types.h"
#include "dng_stream.h"
#include "dng_exceptions.h"
#include "dng_utils.h"

/*****************************************************************************/

#if qDNGValidate

/*****************************************************************************/

struct dng_name_table
	{
	uint32 key;
	const char *name;
	};

/*****************************************************************************/

static const char * LookupName (uint32 key,
						 		const dng_name_table *table,
						 		uint32 table_entries)
	{
	
	for (uint32 index = 0; index < table_entries; index++)
		{
		
		if (key == table [index] . key)
			{
			
			return table [index] . name;
			
			}
			
		}
		
	return NULL;
	
	}

/*****************************************************************************/

const char * LookupParentCode (uint32 parentCode)
	{
	
	const dng_name_table kParentCodeNames [] =
		{
		{	0,							"IFD 0"							},
		{	tcExifIFD,					"Exif IFD"						},
		{	tcGPSInfo,					"GPS IFD"						},
		{	tcInteroperabilityIFD,		"Interoperability IFD"			},
		{	tcKodakDCRPrivateIFD,		"Kodak DCR Private IFD"			},
		{	tcKodakKDCPrivateIFD,		"Kodak KDC Private IFD"			},
		{	tcCanonMakerNote,			"Canon MakerNote"				},
		{	tcEpsonMakerNote,			"Epson MakerNote"				},
		{	tcFujiMakerNote,			"Fuji MakerNote"				},
		{	tcHasselbladMakerNote,		"Hasselblad MakerNote"			},
		{	tcKodakMakerNote,			"Kodak MakerNote"				},
		{	tcKodakMakerNote65280,		"Kodak MakerNote 65280"			},
		{	tcLeicaMakerNote,			"Leica MakerNote"				},
		{	tcMamiyaMakerNote,			"Mamiya MakerNote"				},
		{	tcMinoltaMakerNote,			"Minolta MakerNote"				},
		{	tcNikonMakerNote,			"Nikon MakerNote"				},
		{	tcOlympusMakerNote,			"Olympus MakerNote"				},
		{	tcOlympusMakerNote8208,		"Olympus MakerNote 8208"		},
		{	tcOlympusMakerNote8224,		"Olympus MakerNote 8224"		},
		{	tcOlympusMakerNote8240,		"Olympus MakerNote 8240"		},
		{	tcOlympusMakerNote8256,		"Olympus MakerNote 8256"		},
		{	tcOlympusMakerNote8272,		"Olympus MakerNote 8272"		},
		{	tcOlympusMakerNote12288,	"Olympus MakerNote 12288"		},
		{	tcPanasonicMakerNote,		"Panasonic MakerNote"			},
		{	tcPentaxMakerNote,			"Pentax MakerNote"				},
		{	tcPhaseOneMakerNote,		"Phase One MakerNote"			},
		{	tcRicohMakerNote,			"Ricoh MakerNote"				},
		{	tcRicohMakerNoteCameraInfo,	"Ricoh MakerNote Camera Info"	},
		{	tcSonyMakerNote,			"Sony MakerNote"				},
		{	tcSonyMakerNoteSubInfo,		"Sony MakerNote SubInfo"		},
		{	tcSonyPrivateIFD1,			"Sony Private IFD 1"			},
		{	tcSonyPrivateIFD2,			"Sony Private IFD 2"			},
		{	tcSonyPrivateIFD3A,			"Sony Private IFD 3A"			},
		{	tcSonyPrivateIFD3B,			"Sony Private IFD 3B"			},
		{	tcSonyPrivateIFD3C,			"Sony Private IFD 3C"			},
		{	tcCanonCRW,					"Canon CRW"						},
		{	tcContaxRAW,				"Contax RAW"					},
		{	tcFujiRAF,					"Fuji RAF"						},
		{	tcLeafMOS,					"Leaf MOS"						},
		{	tcMinoltaMRW,				"Minolta MRW"					},
		{	tcPanasonicRAW,				"Panasonic RAW"					},
		{	tcFoveonX3F,				"Foveon X3F"					},
		{	tcJPEG,						"JPEG"							},
		{	tcAdobePSD,					"Adobe PSD"						}
		};

	const char *name = LookupName (parentCode,
								   kParentCodeNames,
								   sizeof (kParentCodeNames    ) /
								   sizeof (kParentCodeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	if (parentCode >= tcFirstSubIFD &&
		parentCode <= tcLastSubIFD)
		{
		
		sprintf (s, "SubIFD %u", (unsigned) (parentCode - tcFirstSubIFD + 1));
		
		}
		
	else if (parentCode >= tcFirstChainedIFD &&
			 parentCode <= tcLastChainedIFD)
		{
		
		sprintf (s, "Chained IFD %u", (unsigned) (parentCode - tcFirstChainedIFD + 1));
		
		}
		
	else
		{
		
		sprintf (s, "ParentIFD %u", (unsigned) parentCode);
		
		}
		
	return s;

	}

/*****************************************************************************/

const char * LookupTagCode (uint32 parentCode,
							uint32 tagCode)
	{
	
	const dng_name_table kTagNames [] =
		{
		{	tcNewSubFileType,					"NewSubFileType"				},
		{	tcSubFileType,						"SubFileType"					},
		{	tcImageWidth,						"ImageWidth"					},
		{	tcImageLength,						"ImageLength"					},
		{	tcBitsPerSample,					"BitsPerSample"					},
		{	tcCompression,						"Compression"					},
		{	tcPhotometricInterpretation,		"PhotometricInterpretation"		},
		{	tcThresholding,						"Thresholding"					},
		{	tcCellWidth,						"CellWidth"						},
		{	tcCellLength,						"CellLength"					},
		{	tcFillOrder,						"FillOrder"						},
		{	tcImageDescription,					"ImageDescription"				},
		{	tcMake,								"Make"							},
		{	tcModel,							"Model"							},
		{	tcStripOffsets,						"StripOffsets"					},
		{	tcOrientation,						"Orientation"					},
		{	tcSamplesPerPixel,					"SamplesPerPixel"				},
		{	tcRowsPerStrip,						"RowsPerStrip"					},
		{	tcStripByteCounts,					"StripByteCounts"				},
		{	tcMinSampleValue,					"MinSampleValue"				},
		{	tcMaxSampleValue,					"MaxSampleValue"				},
		{	tcXResolution,						"XResolution"					},
		{	tcYResolution,						"YResolution"					},
		{	tcPlanarConfiguration,				"PlanarConfiguration"			},
		{	tcFreeOffsets,						"FreeOffsets"					},
		{	tcFreeByteCounts,					"FreeByteCounts"				},
		{	tcGrayResponseUnit,					"GrayResponseUnit"				},
		{	tcGrayResponseCurve,				"GrayResponseCurve"				},
		{	tcResolutionUnit,					"ResolutionUnit"				},
		{	tcTransferFunction,					"TransferFunction"				},
		{	tcSoftware,							"Software"						},
		{	tcDateTime,							"DateTime"						},
		{	tcArtist,							"Artist"						},
		{	tcHostComputer,						"HostComputer"					},
		{	tcWhitePoint,						"WhitePoint"					},
		{	tcPrimaryChromaticities,			"PrimaryChromaticities"			},
		{	tcColorMap,							"ColorMap"						},
		{	tcTileWidth,						"TileWidth"						},
		{	tcTileLength,						"TileLength"					},
		{	tcTileOffsets,						"TileOffsets"					},
		{	tcTileByteCounts,					"TileByteCounts"				},
		{	tcSubIFDs,							"SubIFDs"						},
		{	tcExtraSamples,						"ExtraSamples"					},
		{	tcSampleFormat,						"SampleFormat"					},
		{	tcJPEGTables,						"JPEGTables"					},
		{	tcJPEGProc,							"JPEGProc"						},
		{	tcJPEGInterchangeFormat,			"JPEGInterchangeFormat"			},
		{	tcJPEGInterchangeFormatLength,		"JPEGInterchangeFormatLength"	},
		{	tcYCbCrCoefficients,				"YCbCrCoefficients"				},
		{	tcYCbCrSubSampling,					"YCbCrSubSampling"				},
		{	tcYCbCrPositioning,					"YCbCrPositioning"				},
		{	tcReferenceBlackWhite,				"ReferenceBlackWhite"			},
		{	tcXMP,								"XMP"							},
		{	tcKodakCameraSerialNumber,			"KodakCameraSerialNumber"		},
		{	tcCFARepeatPatternDim,				"CFARepeatPatternDim"			},
		{	tcCFAPattern,						"CFAPattern"					},
		{	tcBatteryLevel,						"BatteryLevel"					},
		{	tcKodakDCRPrivateIFD,				"KodakDCRPrivateIFD"			},
		{	tcCopyright,						"Copyright"						},
		{	tcExposureTime,						"ExposureTime"					},
		{	tcFNumber,							"FNumber"						},
		{	tcIPTC_NAA,							"IPTC/NAA"						},
		{	tcLeafPKTS,							"LeafPKTS"						},
		{	tcAdobeData,						"AdobeData"						},
		{	tcExifIFD,							"ExifIFD"						},
		{	tcICCProfile,						"ICCProfile"					},
		{	tcExposureProgram,					"ExposureProgram"				},
		{	tcSpectralSensitivity,				"SpectralSensitivity"			},
		{	tcGPSInfo,							"GPSInfo"						},
		{	tcISOSpeedRatings,					"ISOSpeedRatings"				},
		{	tcOECF,								"OECF"							},
		{	tcInterlace,						"Interlace"						},
		{	tcTimeZoneOffset,					"TimeZoneOffset"				},
		{	tcSelfTimerMode,					"SelfTimerMode"					},
		{	tcSensitivityType,					"SensitivityType"				},
		{	tcStandardOutputSensitivity,		"StandardOutputSensitivity"		},
		{	tcRecommendedExposureIndex,			"RecommendedExposureIndex"		},
		{	tcISOSpeed,							"ISOSpeed"						},
		{	tcISOSpeedLatitudeyyy,				"ISOSpeedLatitudeyyy"			},
		{	tcISOSpeedLatitudezzz,				"ISOSpeedLatitudezzz"			},
		{	tcExifVersion,						"ExifVersion"					},
		{	tcDateTimeOriginal,					"DateTimeOriginal"				},
		{	tcDateTimeDigitized,				"DateTimeDigitized"				},
		{	tcComponentsConfiguration,			"ComponentsConfiguration"		},
		{	tcCompressedBitsPerPixel,			"CompressedBitsPerPixel"		},
		{	tcShutterSpeedValue,				"ShutterSpeedValue"				},
		{	tcApertureValue,					"ApertureValue"					},
		{	tcBrightnessValue,					"BrightnessValue"				},
		{	tcExposureBiasValue,				"ExposureBiasValue"				},
		{	tcMaxApertureValue,					"MaxApertureValue"				},
		{	tcSubjectDistance,					"SubjectDistance"				},
		{	tcMeteringMode,						"MeteringMode"					},
		{	tcLightSource,						"LightSource"					},
		{	tcFlash,							"Flash"							},
		{	tcFocalLength,						"FocalLength"					},
		{	tcFlashEnergy,						"FlashEnergy"					},
		{	tcSpatialFrequencyResponse,			"SpatialFrequencyResponse"		},
		{	tcNoise,							"Noise"							},
		{	tcFocalPlaneXResolution,			"FocalPlaneXResolution"			},
		{	tcFocalPlaneYResolution,			"FocalPlaneYResolution"			},
		{	tcFocalPlaneResolutionUnit,			"FocalPlaneResolutionUnit"		},
		{	tcImageNumber,						"ImageNumber"					},
		{	tcSecurityClassification,			"SecurityClassification"		},
		{	tcImageHistory,						"ImageHistory"					},
		{	tcSubjectArea,						"SubjectArea"					},
		{	tcExposureIndex,					"ExposureIndex"					},
		{	tcTIFF_EP_StandardID,				"TIFF/EPStandardID"				},
		{	tcSensingMethod,					"SensingMethod"					},
		{	tcMakerNote,						"MakerNote"						},
		{	tcUserComment,						"UserComment"					},
		{	tcSubsecTime,						"SubsecTime"					},
		{	tcSubsecTimeOriginal,				"SubsecTimeOriginal"			},
		{	tcSubsecTimeDigitized,				"SubsecTimeDigitized"			},
		{	tcAdobeLayerData,					"AdobeLayerData"				},
		{	tcFlashPixVersion,					"FlashPixVersion"				},
		{	tcColorSpace,						"ColorSpace"					},
		{	tcPixelXDimension,					"PixelXDimension"				},
		{	tcPixelYDimension,					"PixelYDimension"				},
		{	tcRelatedSoundFile,					"RelatedSoundFile"				},
		{	tcInteroperabilityIFD,				"InteroperabilityIFD"			},
		{	tcFlashEnergyExif,					"FlashEnergyExif"				},
		{	tcSpatialFrequencyResponseExif,		"SpatialFrequencyResponseExif"	},
		{	tcFocalPlaneXResolutionExif,		"FocalPlaneXResolutionExif"		},
		{	tcFocalPlaneYResolutionExif,		"FocalPlaneYResolutionExif"		},
		{	tcFocalPlaneResolutionUnitExif,		"FocalPlaneResolutionUnitExif"	},
		{	tcSubjectLocation,					"SubjectLocation"				},
		{	tcExposureIndexExif,				"ExposureIndexExif"				},
		{	tcSensingMethodExif,				"SensingMethodExif"				},
		{	tcFileSource,						"FileSource"					},
		{	tcSceneType,						"SceneType"						},
		{	tcCFAPatternExif,					"CFAPatternExif"				},
		{	tcCustomRendered,					"CustomRendered"				},
		{	tcExposureMode,						"ExposureMode"					},
		{	tcWhiteBalance,						"WhiteBalance"					},
		{	tcDigitalZoomRatio,					"DigitalZoomRatio"				},
		{	tcFocalLengthIn35mmFilm,			"FocalLengthIn35mmFilm"			},
		{	tcSceneCaptureType,					"SceneCaptureType"				},
		{	tcGainControl,						"GainControl"					},
		{	tcContrast,							"Contrast"						},
		{	tcSaturation,						"Saturation"					},
		{	tcSharpness,						"Sharpness"						},
		{	tcDeviceSettingDescription,			"DeviceSettingDescription"		},
		{	tcSubjectDistanceRange,				"SubjectDistanceRange"			},
		{	tcImageUniqueID,					"ImageUniqueID"					},
		{	tcCameraOwnerNameExif,				"CameraOwnerNameExif"			},
		{	tcCameraSerialNumberExif,			"CameraSerialNumberExif"		},
		{	tcLensSpecificationExif,			"LensSpecificationExif"			},
		{	tcLensMakeExif,						"LensMakeExif"					},
		{	tcLensModelExif,					"LensModelExif"					},
		{	tcLensSerialNumberExif,				"LensSerialNumberExif"			},
		{	tcGamma,							"Gamma"							},
		{	tcPrintImageMatchingInfo,			"PrintImageMatchingInfo"		},
		{	tcDNGVersion,						"DNGVersion"					},
		{	tcDNGBackwardVersion,				"DNGBackwardVersion"			},
		{	tcUniqueCameraModel,				"UniqueCameraModel"				},
		{	tcLocalizedCameraModel,				"LocalizedCameraModel"			},
		{	tcCFAPlaneColor,					"CFAPlaneColor"					},
		{	tcCFALayout,						"CFALayout"						},
		{	tcLinearizationTable,				"LinearizationTable"			},
		{	tcBlackLevelRepeatDim,				"BlackLevelRepeatDim"			},
		{	tcBlackLevel,						"BlackLevel"					},
		{	tcBlackLevelDeltaH,					"BlackLevelDeltaH"				},
		{	tcBlackLevelDeltaV,					"BlackLevelDeltaV"				},
		{	tcWhiteLevel,						"WhiteLevel"					},
		{	tcDefaultScale,						"DefaultScale"					},
		{	tcDefaultCropOrigin,				"DefaultCropOrigin"				},
		{	tcDefaultCropSize,					"DefaultCropSize"				},
		{	tcDefaultUserCrop,					"DefaultUserCrop"				},
		{	tcColorMatrix1,						"ColorMatrix1"					},
		{	tcColorMatrix2,						"ColorMatrix2"					},
		{	tcCameraCalibration1,				"CameraCalibration1"			},
		{	tcCameraCalibration2,				"CameraCalibration2"			},
		{	tcReductionMatrix1,					"ReductionMatrix1"				},
		{	tcReductionMatrix2,					"ReductionMatrix2"				},
		{	tcAnalogBalance,					"AnalogBalance"					},
		{	tcAsShotNeutral,					"AsShotNeutral"					},
		{	tcAsShotWhiteXY,					"AsShotWhiteXY"					},
		{	tcBaselineExposure,					"BaselineExposure"				},
		{	tcBaselineNoise,					"BaselineNoise"					},
		{	tcBaselineSharpness,				"BaselineSharpness"				},
		{	tcBayerGreenSplit,					"BayerGreenSplit"				},
		{	tcLinearResponseLimit,				"LinearResponseLimit"			},
		{	tcCameraSerialNumber,				"CameraSerialNumber"			},
		{	tcLensInfo,							"LensInfo"						},
		{	tcChromaBlurRadius,					"ChromaBlurRadius"				},
		{	tcAntiAliasStrength,				"AntiAliasStrength"				},
		{	tcShadowScale,						"ShadowScale"					},
		{	tcDNGPrivateData,					"DNGPrivateData"				},
		{	tcMakerNoteSafety,					"MakerNoteSafety"				},
		{	tcCalibrationIlluminant1,			"CalibrationIlluminant1"		},
		{	tcCalibrationIlluminant2,			"CalibrationIlluminant2"		},
		{	tcBestQualityScale,					"BestQualityScale"				},
		{	tcRawDataUniqueID,					"RawDataUniqueID"				},
		{	tcOriginalRawFileName,				"OriginalRawFileName"			},
		{	tcOriginalRawFileData,				"OriginalRawFileData"			},
		{	tcActiveArea,						"ActiveArea"					},
		{	tcMaskedAreas,						"MaskedAreas"					},
		{	tcAsShotICCProfile,					"AsShotICCProfile"				},
		{	tcAsShotPreProfileMatrix,			"AsShotPreProfileMatrix"		},
		{	tcCurrentICCProfile,				"CurrentICCProfile"				},
		{	tcCurrentPreProfileMatrix,			"CurrentPreProfileMatrix"		},
		{	tcColorimetricReference,			"ColorimetricReference"			},
		{	tcCameraCalibrationSignature,		"CameraCalibrationSignature"	},
		{	tcProfileCalibrationSignature,		"ProfileCalibrationSignature"	},
		{	tcExtraCameraProfiles,				"ExtraCameraProfiles"			},
		{	tcAsShotProfileName,				"AsShotProfileName"				},
		{	tcNoiseReductionApplied,			"NoiseReductionApplied"			},
		{	tcProfileName,						"ProfileName"					},
		{	tcProfileHueSatMapDims,				"ProfileHueSatMapDims"			},
		{	tcProfileHueSatMapData1,			"ProfileHueSatMapData1"			},
		{	tcProfileHueSatMapData2,			"ProfileHueSatMapData2"			},
		{	tcProfileHueSatMapEncoding,			"ProfileHueSatMapEncoding"		},
		{	tcProfileToneCurve,					"ProfileToneCurve"				},
		{	tcProfileEmbedPolicy,				"ProfileEmbedPolicy"			},
		{	tcProfileCopyright,					"ProfileCopyright"				},
		{	tcForwardMatrix1,					"ForwardMatrix1"				},
		{	tcForwardMatrix2,					"ForwardMatrix2"				},
		{	tcPreviewApplicationName,			"PreviewApplicationName"		},
		{	tcPreviewApplicationVersion,		"PreviewApplicationVersion"		},
		{	tcPreviewSettingsName,				"PreviewSettingsName"		    },
		{	tcPreviewSettingsDigest,			"PreviewSettingsDigest"		    },
		{	tcPreviewColorSpace,				"PreviewColorSpace"				},
		{	tcPreviewDateTime,					"PreviewDateTime"				},
		{	tcRawImageDigest,					"RawImageDigest"				},
		{	tcOriginalRawFileDigest,			"OriginalRawFileDigest"			},
		{	tcSubTileBlockSize,					"SubTileBlockSize"				},
		{	tcRowInterleaveFactor,				"RowInterleaveFactor"			},
		{	tcProfileLookTableDims,				"ProfileLookTableDims"			},
		{	tcProfileLookTableData,				"ProfileLookTableData"			},
		{	tcProfileLookTableEncoding,			"ProfileLookTableEncoding"		},
		{	tcBaselineExposureOffset,			"BaselineExposureOffset"		},
		{	tcDefaultBlackRender,				"DefaultBlackRender"			},
		{	tcOpcodeList1,						"OpcodeList1"					},
		{	tcOpcodeList2,						"OpcodeList2"					},
		{	tcOpcodeList3,						"OpcodeList3"					},
		{	tcNoiseProfile,						"NoiseProfile"					},
		{	tcOriginalDefaultFinalSize,			"OriginalDefaultFinalSize"		},
		{	tcOriginalBestQualityFinalSize,		"OriginalBestQualityFinalSize"	},
		{	tcOriginalDefaultCropSize,			"OriginalDefaultCropSize"		},
		{	tcProfileHueSatMapEncoding,			"ProfileHueSatMapEncoding"		},
		{	tcProfileLookTableEncoding,			"ProfileLookTableEncoding"		},
		{	tcBaselineExposureOffset,			"BaselineExposureOffset"		},
		{	tcDefaultBlackRender,				"DefaultBlackRender"			},
		{	tcNewRawImageDigest,				"NewRawImageDigest"				},
		{	tcRawToPreviewGain,					"RawToPreviewGain"				},
		{	tcCacheBlob,						"CacheBlob"						},
		{	tcKodakKDCPrivateIFD,				"KodakKDCPrivateIFD"			}
		};

	const dng_name_table kGPSTagNames [] =
		{
		{	tcGPSVersionID,					"GPSVersionID"			},
		{	tcGPSLatitudeRef,				"GPSLatitudeRef"		},
		{	tcGPSLatitude,					"GPSLatitude"			},
		{	tcGPSLongitudeRef,				"GPSLongitudeRef"		},
		{	tcGPSLongitude,					"GPSLongitude"			},
		{	tcGPSAltitudeRef,				"GPSAltitudeRef"		},
		{	tcGPSAltitude,					"GPSAltitude"			},
		{	tcGPSTimeStamp,					"GPSTimeStamp"			},
		{	tcGPSSatellites,				"GPSSatellites"			},
		{	tcGPSStatus,					"GPSStatus"				},
		{	tcGPSMeasureMode,				"GPSMeasureMode"		},
		{	tcGPSDOP,						"GPSDOP"				},
		{	tcGPSSpeedRef,					"GPSSpeedRef"			},
		{	tcGPSSpeed,						"GPSSpeed"				},
		{	tcGPSTrackRef,					"GPSTrackRef"			},
		{	tcGPSTrack,						"GPSTrack"				},
		{	tcGPSImgDirectionRef,			"GPSImgDirectionRef"	},
		{	tcGPSImgDirection,				"GPSImgDirection"		},
		{	tcGPSMapDatum,					"GPSMapDatum"			},
		{	tcGPSDestLatitudeRef,			"GPSDestLatitudeRef"	},
		{	tcGPSDestLatitude,				"GPSDestLatitude"		},
		{	tcGPSDestLongitudeRef,			"GPSDestLongitudeRef"	},
		{	tcGPSDestLongitude,				"GPSDestLongitude"		},
		{	tcGPSDestBearingRef,			"GPSDestBearingRef"		},
		{	tcGPSDestBearing,				"GPSDestBearing"		},
		{	tcGPSDestDistanceRef,			"GPSDestDistanceRef"	},
		{	tcGPSDestDistance,				"GPSDestDistance"		},
		{	tcGPSProcessingMethod,			"GPSProcessingMethod"	},
		{	tcGPSAreaInformation,			"GPSAreaInformation"	},
		{	tcGPSDateStamp,					"GPSDateStamp"			},
		{	tcGPSDifferential,				"GPSDifferential"		},
		{	tcGPSHPositioningError,			"GPSHPositioningError"	},
		};

	const dng_name_table kInteroperabilityTagNames [] =
		{
		{	tcInteroperabilityIndex,		"InteroperabilityIndex"		},
		{	tcInteroperabilityVersion,		"InteroperabilityVersion"	},
		{	tcRelatedImageFileFormat,		"RelatedImageFileFormat"	},
		{	tcRelatedImageWidth,			"RelatedImageWidth"			},
		{	tcRelatedImageLength,			"RelatedImageLength"		}
		};

	const dng_name_table kFujiTagNames [] =
		{
		{	tcFujiHeader,					"FujiHeader"	},
		{	tcFujiRawInfo1,					"FujiRawInfo1"	},
		{	tcFujiRawInfo2,					"FujiRawInfo2"	}
		};

	const dng_name_table kContaxTagNames [] =
		{
		{	tcContaxHeader,					"ContaxHeader"	}
		};

	const char *name = NULL;
	
	if (parentCode == 0         										 ||
		parentCode == tcExifIFD 										 ||
		parentCode == tcLeafMOS 										 ||
		(parentCode >= tcFirstSubIFD     && parentCode <= tcLastSubIFD)  ||
		(parentCode >= tcFirstChainedIFD && parentCode <= tcLastChainedIFD))
		{
		
		name = LookupName (tagCode,
						   kTagNames,
						   sizeof (kTagNames    ) /
						   sizeof (kTagNames [0]));
						   
		}
		
	else if (parentCode == tcGPSInfo)
		{
		
		name = LookupName (tagCode,
						   kGPSTagNames,
						   sizeof (kGPSTagNames    ) /
						   sizeof (kGPSTagNames [0]));

		}
		
	else if (parentCode == tcInteroperabilityIFD)
		{
								   
		name = LookupName (tagCode,
						   kInteroperabilityTagNames,
						   sizeof (kInteroperabilityTagNames    ) /
						   sizeof (kInteroperabilityTagNames [0]));
						   
		}

	else if (parentCode == tcFujiRAF)
		{
								   
		name = LookupName (tagCode,
						   kFujiTagNames,
						   sizeof (kFujiTagNames    ) /
						   sizeof (kFujiTagNames [0]));
						   
		}

	else if (parentCode == tcContaxRAW)
		{
								   
		name = LookupName (tagCode,
						   kContaxTagNames,
						   sizeof (kContaxTagNames    ) /
						   sizeof (kContaxTagNames [0]));
						   
		}

	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	if (parentCode == tcCanonCRW)
		{
		sprintf (s, "CRW_%04X", (unsigned) tagCode);
		}
		
	else if (parentCode == tcMinoltaMRW)
		{
		
		char c1 = (char) ((tagCode >> 24) & 0xFF);
		char c2 = (char) ((tagCode >> 16) & 0xFF);
		char c3 = (char) ((tagCode >>  8) & 0xFF);
		char c4 = (char) ((tagCode      ) & 0xFF);
		
		if (c1 < ' ') c1 = '_';
		if (c2 < ' ') c2 = '_';
		if (c3 < ' ') c3 = '_';
		if (c4 < ' ') c4 = '_';

		sprintf (s, "MRW%c%c%c%c", c1, c2, c3, c4);
		
		}
		
	else if (parentCode == tcFujiRawInfo1)
		{
		sprintf (s, "RAF1_%04X", (unsigned) tagCode);
		}
		
	else if (parentCode == tcFujiRawInfo2)
		{
		sprintf (s, "RAF2_%04X", (unsigned) tagCode);
		}
		
	else
		{
		sprintf (s, "Tag%u", (unsigned) tagCode);
		}
	
	return s;

	}

/*****************************************************************************/

const char * LookupTagType (uint32 tagType)
	{
	
	const dng_name_table kTagTypeNames [] =
		{
		{	ttByte,			"Byte"		},
		{	ttAscii,		"ASCII"		},
		{	ttShort,		"Short"		},
		{	ttLong,			"Long"		},
		{	ttRational,		"Rational"	},
		{	ttSByte,		"SByte"		},
		{	ttUndefined,	"Undefined"	},
		{	ttSShort,		"SShort"	},
		{	ttSLong,		"SLong"		},
		{	ttSRational,	"SRational"	},
		{	ttFloat,		"Float"		},
		{	ttDouble,		"Double"	},
		{	ttIFD,			"IFD"		},
		{	ttUnicode,		"Unicode"	},
		{	ttComplex,		"Complex"	}
		};

	const char *name = LookupName (tagType,
								   kTagTypeNames,
								   sizeof (kTagTypeNames    ) /
								   sizeof (kTagTypeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "Type%u", (unsigned) tagType);
	
	return s;

	}

/*****************************************************************************/

const char * LookupNewSubFileType (uint32 key)
	{
	
	const dng_name_table kNewSubFileTypeNames [] =
		{
		{	sfMainImage			, "Main Image"			},
		{	sfPreviewImage		, "Preview Image"		},
		{	sfTransparencyMask	, "Transparency Mask"	},
		{	sfPreviewMask		, "Preview Mask"		},
		{	sfAltPreviewImage	, "Alt Preview Image"	}
		};

	const char *name = LookupName (key,
								   kNewSubFileTypeNames,
								   sizeof (kNewSubFileTypeNames    ) /
								   sizeof (kNewSubFileTypeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupCompression (uint32 key)
	{
	
	const dng_name_table kCompressionNames [] =
		{
		{	ccUncompressed,		"Uncompressed"	},
		{	ccLZW,				"LZW"			},
		{	ccOldJPEG,			"Old JPEG"		},
		{	ccJPEG,				"JPEG"			},
		{	ccDeflate,			"Deflate"		},
		{	ccPackBits,			"PackBits"		},
		{	ccOldDeflate,		"OldDeflate"	},
		{	ccLossyJPEG,		"Lossy JPEG"	}
		};

	const char *name = LookupName (key,
								   kCompressionNames,
								   sizeof (kCompressionNames    ) /
								   sizeof (kCompressionNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupPredictor (uint32 key)
	{
	
	const dng_name_table kPredictorNames [] =
		{
		{	cpNullPredictor,			"NullPredictor"				},
		{	cpHorizontalDifference,		"HorizontalDifference"		},
		{	cpFloatingPoint,			"FloatingPoint"				},
		{	cpHorizontalDifferenceX2,	"HorizontalDifferenceX2"	},
		{	cpHorizontalDifferenceX4,	"HorizontalDifferenceX4"	},
		{	cpFloatingPointX2,			"FloatingPointX2"			},
		{	cpFloatingPointX4,			"FloatingPointX4"			}
		};

	const char *name = LookupName (key,
								   kPredictorNames,
								   sizeof (kPredictorNames    ) /
								   sizeof (kPredictorNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupSampleFormat (uint32 key)
	{
	
	const dng_name_table kSampleFormatNames [] =
		{
		{	sfUnsignedInteger,	"UnsignedInteger"	},
		{	sfSignedInteger,	"SignedInteger"		},
		{	sfFloatingPoint,	"FloatingPoint"		},
		{	sfUndefined,		"Undefined"			}
		};

	const char *name = LookupName (key,
								   kSampleFormatNames,
								   sizeof (kSampleFormatNames    ) /
								   sizeof (kSampleFormatNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupPhotometricInterpretation (uint32 key)
	{
	
	const dng_name_table kPhotometricInterpretationNames [] =
		{
		{	piWhiteIsZero, 			"WhiteIsZero"		},
		{	piBlackIsZero,			"BlackIsZero"		},
		{	piRGB,					"RGB"				},
		{	piRGBPalette,			"RGBPalette"		},
		{	piTransparencyMask,		"TransparencyMask"	},
		{	piCMYK,					"CMYK"				},
		{	piYCbCr,				"YCbCr"				},
		{	piCIELab,				"CIELab"			},
		{	piICCLab,				"ICCLab"			},
		{	piCFA,					"CFA"				},
		{	piLinearRaw,			"LinearRaw"			}
		};

	const char *name = LookupName (key,
								   kPhotometricInterpretationNames,
								   sizeof (kPhotometricInterpretationNames    ) /
								   sizeof (kPhotometricInterpretationNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupOrientation (uint32 key)
	{
	
	const dng_name_table kOrientationNames [] =
		{
		{	1, 	"1 - 0th row is top, 0th column is left"		},
		{	2,	"2 - 0th row is top, 0th column is right"		},
		{	3,	"3 - 0th row is bottom, 0th column is right"	},
		{	4,	"4 - 0th row is bottom, 0th column is left"		},
		{	5,	"5 - 0th row is left, 0th column is top"		},
		{	6,	"6 - 0th row is right, 0th column is top"		},
		{	7,	"7 - 0th row is right, 0th column is bottom"	},
		{	8,	"8 - 0th row is left, 0th column is bottom"		},
		{	9,	"9 - unknown"									}
		};

	const char *name = LookupName (key,
								   kOrientationNames,
								   sizeof (kOrientationNames    ) /
								   sizeof (kOrientationNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupResolutionUnit (uint32 key)
	{
	
	const dng_name_table kResolutionUnitNames [] =
		{
		{	ruNone, 	"None"			},
		{	ruInch,		"Inch"			},
		{	ruCM,		"cm"			},
		{	ruMM,		"mm"			},
		{	ruMicroM,	"Micrometer"	}
		};

	const char *name = LookupName (key,
								   kResolutionUnitNames,
								   sizeof (kResolutionUnitNames    ) /
								   sizeof (kResolutionUnitNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupCFAColor (uint32 key)
	{
	
	const dng_name_table kCFAColorNames [] =
		{
		{	0, "Red"		},
		{	1, "Green"		},
		{	2, "Blue"		},
		{	3, "Cyan"		},
		{	4, "Magenta"	},
		{	5, "Yellow"		},
		{	6, "White"		}
		};
		
	const char *name = LookupName (key,
								   kCFAColorNames,
								   sizeof (kCFAColorNames    ) /
								   sizeof (kCFAColorNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "Color%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupSensingMethod (uint32 key)
	{
	
	const dng_name_table kSensingMethodNames [] =
		{
		{	0, "Undefined"				},
		{	1, "MonochromeArea"			},
		{	2, "OneChipColorArea"		},
		{	3, "TwoChipColorArea"		},
		{	4, "ThreeChipColorArea"		},
		{	5, "ColorSequentialArea"	},
		{	6, "MonochromeLinear"		},
		{	7, "TriLinear"				},
		{	8, "ColorSequentialLinear"	}
		};
		
	const char *name = LookupName (key,
								   kSensingMethodNames,
								   sizeof (kSensingMethodNames    ) /
								   sizeof (kSensingMethodNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupExposureProgram (uint32 key)
	{
	
	const dng_name_table kExposureProgramNames [] =
		{
		{	epUnidentified,		"Unidentified"		},
		{	epManual,			"Manual"			},
		{	epProgramNormal,	"Program Normal"	},
		{	epAperturePriority,	"Aperture Priority"	},
		{	epShutterPriority, 	"Shutter Priority"	},
		{	epProgramCreative,	"Program Creative"	},
		{	epProgramAction,	"Program Action"	},
		{	epPortraitMode,		"Portrait Mode"		},
		{	epLandscapeMode,	"Landscape Mode"	}	
		};
		
	const char *name = LookupName (key,
								   kExposureProgramNames,
								   sizeof (kExposureProgramNames    ) /
								   sizeof (kExposureProgramNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupMeteringMode (uint32 key)
	{
	
	const dng_name_table kMeteringModeNames [] =
		{
		{	mmUnidentified,   			"Unknown"				},
		{	mmAverage,   				"Average"				},
		{	mmCenterWeightedAverage,	"CenterWeightedAverage"	},
		{	mmSpot,   					"Spot"					},
		{	mmMultiSpot,   				"MultiSpot"				},
		{	mmPattern,   				"Pattern"				},
		{	mmPartial,   				"Partial"				},
		{	mmOther, 					"Other"					}
		};
		
	const char *name = LookupName (key,
								   kMeteringModeNames,
								   sizeof (kMeteringModeNames    ) /
								   sizeof (kMeteringModeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
	
	sprintf (s, "%u", (unsigned) key);
	
	return s;

	}

/*****************************************************************************/

const char * LookupLightSource (uint32 key)
	{
	
	const dng_name_table kLightSourceNames [] =
		{
		{	lsUnknown,				"Unknown"									},
		{	lsDaylight,				"Daylight"									},
		{	lsFluorescent,			"Fluorescent"								},
		{	lsTungsten,				"Tungsten (incandescent light)"				},
		{	lsFlash,				"Flash"										},
		{	lsFineWeather,			"Fine weather"								},
		{	lsCloudyWeather,		"Cloudy weather"							},
		{	lsShade,				"Shade"										},
		{	lsDaylightFluorescent,	"Daylight fluorescent (D 5700 - 7100K)"		},
		{	lsDayWhiteFluorescent,	"Day white fluorescent (N 4600 - 5500K)"	},
		{	lsCoolWhiteFluorescent,	"Cool white fluorescent (W 3800 - 4500K)"	},
		{	lsWhiteFluorescent,		"White fluorescent (WW 3250 - 3800K)"		},
		{	lsWarmWhiteFluorescent, "Warm white fluorescent (L 2600 - 3250K)"	},
		{	lsStandardLightA,		"Standard light A"							},
		{	lsStandardLightB,		"Standard light B"							},
		{	lsStandardLightC,		"Standard light C"							},
		{	lsD55,					"D55"										},
		{	lsD65,					"D65"										},
		{	lsD75,					"D75"										},
		{	lsD50,					"D50"										},
		{	lsISOStudioTungsten,	"ISO studio tungsten"						},
		{	lsOther,				"Other"										}
		};
		
	const char *name = LookupName (key,
								   kLightSourceNames,
								   sizeof (kLightSourceNames    ) /
								   sizeof (kLightSourceNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	if (key & 0x08000)
		{
		
		sprintf (s, "%uK", (unsigned) (key & 0x7FFF));
		
		}
		
	else
		{
		
		sprintf (s, "%u", (unsigned) key);
		
		}
		
	return s;

	}

/*****************************************************************************/

const char * LookupColorSpace (uint32 key)
	{
	
	const dng_name_table kColorSpaceNames [] =
		{
		{	1,		"sRGB"			},
		{	0xFFFF,	"Uncalibrated"	}
		};
		
	const char *name = LookupName (key,
								   kColorSpaceNames,
								   sizeof (kColorSpaceNames    ) /
								   sizeof (kColorSpaceNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupFileSource (uint32 key)
	{
	
	const dng_name_table kFileSourceNames [] =
		{
		{	3,		"DSC"	}
		};
		
	const char *name = LookupName (key,
								   kFileSourceNames,
								   sizeof (kFileSourceNames    ) /
								   sizeof (kFileSourceNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupSceneType (uint32 key)
	{
	
	const dng_name_table kSceneTypeNames [] =
		{
		{	1,	"A directly photographed image"		}
		};
		
	const char *name = LookupName (key,
								   kSceneTypeNames,
								   sizeof (kSceneTypeNames    ) /
								   sizeof (kSceneTypeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupCustomRendered (uint32 key)
	{
	
	const dng_name_table kCustomRenderedNames [] =
		{
		{	0,		"Normal process"	},
		{	1,		"Custom process"	}
		};
		
	const char *name = LookupName (key,
								   kCustomRenderedNames,
								   sizeof (kCustomRenderedNames    ) /
								   sizeof (kCustomRenderedNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupExposureMode (uint32 key)
	{
	
	const dng_name_table kExposureModeNames [] =
		{
		{	0,	"Auto exposure"		},
		{	1,	"Manual exposure"	},
		{	2,	"Auto bracket"		}
		};
		
	const char *name = LookupName (key,
								   kExposureModeNames,
								   sizeof (kExposureModeNames    ) /
								   sizeof (kExposureModeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupWhiteBalance (uint32 key)
	{
	
	const dng_name_table kWhiteBalanceNames [] =
		{
		{	0,	"Auto white balance"	},
		{	1,	"Manual white balance"	}
		};
		
	const char *name = LookupName (key,
								   kWhiteBalanceNames,
								   sizeof (kWhiteBalanceNames    ) /
								   sizeof (kWhiteBalanceNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupSceneCaptureType (uint32 key)
	{
	
	const dng_name_table kSceneCaptureTypeNames [] =
		{
		{	0,	"Standard"		},
		{	1,	"Landscape"		},
		{	2,	"Portrait"		},
		{	3,	"Night scene"	}
		};
		
	const char *name = LookupName (key,
								   kSceneCaptureTypeNames,
								   sizeof (kSceneCaptureTypeNames    ) /
								   sizeof (kSceneCaptureTypeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupGainControl (uint32 key)
	{
	
	const dng_name_table kGainControlNames [] =
		{
		{	0,	"None"				},
		{	1,	"Low gain up"		},
		{	2,	"High gain up"		},
		{	3,	"Low gain down"		},
		{	4,	"High gain down"	}
		};
		
	const char *name = LookupName (key,
								   kGainControlNames,
								   sizeof (kGainControlNames    ) /
								   sizeof (kGainControlNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupContrast (uint32 key)
	{
	
	const dng_name_table kContrastNames [] =
		{
		{	0,	"Normal"	},
		{	1,	"Soft"		},
		{	2,	"Hard"		}
		};
		
	const char *name = LookupName (key,
								   kContrastNames,
								   sizeof (kContrastNames    ) /
								   sizeof (kContrastNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupSaturation (uint32 key)
	{
	
	const dng_name_table kSaturationNames [] =
		{
		{	0,	"Normal"			},
		{	1,	"Low saturation"	},
		{	2,	"High saturation"	}
		};
		
	const char *name = LookupName (key,
								   kSaturationNames,
								   sizeof (kSaturationNames    ) /
								   sizeof (kSaturationNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupSharpness (uint32 key)
	{
	
	const dng_name_table kSharpnessNames [] =
		{
		{	0,	"Normal"	},
		{	1,	"Soft"		},
		{	2,	"Hard"		}
		};
		
	const char *name = LookupName (key,
								   kSharpnessNames,
								   sizeof (kSharpnessNames    ) /
								   sizeof (kSharpnessNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupSubjectDistanceRange (uint32 key)
	{
	
	const dng_name_table kSubjectDistanceRangeNames [] =
		{
		{	0,	"Unknown"		},
		{	1,	"Macro"			},
		{	2,	"Close view"	},
		{	3,	"Distant view"	}
		};
		
	const char *name = LookupName (key,
								   kSubjectDistanceRangeNames,
								   sizeof (kSubjectDistanceRangeNames    ) /
								   sizeof (kSubjectDistanceRangeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupComponent (uint32 key)
	{
	
	const dng_name_table kComponentNames [] =
		{
		{	0, "-"	},
		{	1, "Y"	},
		{	2, "Cb"	},
		{	3, "Cr"	},
		{	4, "R"	},
		{	5, "G"	},
		{	6, "B"	}
		};
		
	const char *name = LookupName (key,
								   kComponentNames,
								   sizeof (kComponentNames    ) /
								   sizeof (kComponentNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupCFALayout (uint32 key)
	{
	
	const dng_name_table kCFALayoutNames [] =
		{
		{	1,	"Rectangular (or square) layout"																		},
		{	2,	"Staggered layout A: even columns are offset down by 1/2 row"											},
		{	3,	"Staggered layout B: even columns are offset up by 1/2 row"												},
		{	4,	"Staggered layout C: even rows are offset right by 1/2 column"											},
		{	5,	"Staggered layout D: even rows are offset left by 1/2 column"											},
		{	6,	"Staggered layout E: even rows are offset up by 1/2 row, even columns are offset left by 1/2 column"	},
		{	7,	"Staggered layout F: even rows are offset up by 1/2 row, even columns are offset right by 1/2 column"	},
		{	8,	"Staggered layout G: even rows are offset down by 1/2 row, even columns are offset left by 1/2 column"	},
		{	9,	"Staggered layout H: even rows are offset down by 1/2 row, even columns are offset right by 1/2 column"	}
		};

	const char *name = LookupName (key,
								   kCFALayoutNames,
								   sizeof (kCFALayoutNames    ) /
								   sizeof (kCFALayoutNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupMakerNoteSafety (uint32 key)
	{
	
	const dng_name_table kMakerNoteSafetyNames [] =
		{
		{	0,	"Unsafe"	},
		{	1,	"Safe"		}
		};

	const char *name = LookupName (key,
								   kMakerNoteSafetyNames,
								   sizeof (kMakerNoteSafetyNames    ) /
								   sizeof (kMakerNoteSafetyNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupColorimetricReference (uint32 key)
	{
	
	const dng_name_table kColorimetricReferenceNames [] =
		{
		{	crSceneReferred,	"Scene Referred"	},
		{	crICCProfilePCS,	"ICC Profile PCS"	}
		};

	const char *name = LookupName (key,
								   kColorimetricReferenceNames,
								   sizeof (kColorimetricReferenceNames    ) /
								   sizeof (kColorimetricReferenceNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupPreviewColorSpace (uint32 key)
	{
	
	const dng_name_table kPreviewColorSpaceNames [] =
		{
		{	previewColorSpace_Unknown    ,	"Unknown"			},
		{	previewColorSpace_GrayGamma22,	"Gray Gamma 2.2"	},
		{	previewColorSpace_sRGB       ,	"sRGB"				},
		{	previewColorSpace_AdobeRGB   ,	"Adobe RGB (1998)"	},
		{	previewColorSpace_ProPhotoRGB,	"Pro Photo RGB"	    }
		};

	const char *name = LookupName (key,
								   kPreviewColorSpaceNames,
								   sizeof (kPreviewColorSpaceNames    ) /
								   sizeof (kPreviewColorSpaceNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupJPEGMarker (uint32 key)
	{
	
	const dng_name_table kJPEGMarkerNames [] =
		{
		{	M_TEM,		"TEM"	},
		{	M_SOF0,		"SOF0"	},
		{	M_SOF1,		"SOF1"	},
		{	M_SOF2,		"SOF2"	},
		{	M_SOF3,		"SOF3"	},
		{	M_DHT,		"DHT"	},
		{	M_SOF5,		"SOF5"	},
		{	M_SOF6,		"SOF6"	},
		{	M_SOF7,		"SOF7"	},
		{	M_JPG,		"JPG"	},
		{	M_SOF9,		"SOF9"	},
		{	M_SOF10,	"SOF10"	},
		{	M_SOF11,	"SOF11"	},
		{	M_DAC,		"DAC"	},
		{	M_SOF13,	"SOF13"	},
		{	M_SOF14,	"SOF14"	},
		{	M_SOF15,	"SOF15"	},
		{	M_RST0,		"RST0"	},
		{	M_RST1,		"RST1"	},
		{	M_RST2,		"RST2"	},
		{	M_RST3,		"RST3"	},
		{	M_RST4,		"RST4"	},
		{	M_RST5,		"RST5"	},
		{	M_RST6,		"RST6"	},
		{	M_RST7,		"RST7"	},
		{	M_SOI,		"SOI"	},
		{	M_EOI,		"EOI"	},
		{	M_SOS,		"SOS"	},
		{	M_DQT,		"DQT"	},
		{	M_DNL,		"DNL"	},
		{	M_DRI,		"DRI"	},
		{	M_DHP,		"DHP"	},
		{	M_EXP,		"EXP"	},
		{	M_APP0,		"APP0"	},
		{	M_APP1,		"APP1"	},
		{	M_APP2,		"APP2"	},
		{	M_APP3,		"APP3"	},
		{	M_APP4,		"APP4"	},
		{	M_APP5,		"APP5"	},
		{	M_APP6,		"APP6"	},
		{	M_APP7,		"APP7"	},
		{	M_APP8,		"APP8"	},
		{	M_APP9,		"APP9"	},
		{	M_APP10,	"APP10"	},
		{	M_APP11,	"APP11"	},
		{	M_APP12,	"APP12"	},
		{	M_APP13,	"APP13"	},
		{	M_APP14,	"APP14"	},
		{	M_APP15,	"APP15"	},
		{	M_JPG0,		"JPG0"	},
		{	M_JPG1,		"JPG1"	},
		{	M_JPG2,		"JPG2"	},
		{	M_JPG3,		"JPG3"	},
		{	M_JPG4,		"JPG4"	},
		{	M_JPG5,		"JPG5"	},
		{	M_JPG6,		"JPG6"	},
		{	M_JPG7,		"JPG7"	},
		{	M_JPG8,		"JPG8"	},
		{	M_JPG9,		"JPG9"	},
		{	M_JPG10,	"JPG10"	},
		{	M_JPG11,	"JPG11"	},
		{	M_JPG12,	"JPG12"	},
		{	M_JPG13,	"JPG13"	},
		{	M_COM,		"COM"	},
		{	M_ERROR,	"ERROR"	}
		};

	const char *name = LookupName (key,
								   kJPEGMarkerNames,
								   sizeof (kJPEGMarkerNames    ) /
								   sizeof (kJPEGMarkerNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "0x%02X", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

const char * LookupSensitivityType (uint32 key)
	{
	
	const dng_name_table kSensitivityTypeNames [] =
		{
		{	stUnknown,					 "Unknown"																				},
		{	stStandardOutputSensitivity, "Standard Output Sensitivity (SOS)"													},
		{	stRecommendedExposureIndex,	 "Recommended Exposure Index (REI)"														},
		{	stISOSpeed,					 "ISO Speed"																			},
		{	stSOSandREI,				 "Standard Output Sensitivity (SOS) and Recommended Exposure Index (REI)"				},
		{	stSOSandISOSpeed,			 "Standard Output Sensitivity (SOS) and ISO Speed"										},
		{	stREIandISOSpeed,			 "Recommended Exposure Index (REI) and ISO Speed"										},
		{	stSOSandREIandISOSpeed,		 "Standard Output Sensitivity (SOS) and Recommended Exposure Index (REI) and ISO Speed" },
		};
		
	const char *name = LookupName (key,
								   kSensitivityTypeNames,
								   sizeof (kSensitivityTypeNames	) /
								   sizeof (kSensitivityTypeNames [0]));
								   
	if (name)
		{
		return name;
		}
		
	static char s [32];
		
	sprintf (s, "%u", (unsigned) key);
		
	return s;

	}

/*****************************************************************************/

void DumpHexAscii (dng_stream &stream,
				   uint32 count)
	{
	
	uint32 rows = (count + 15) >> 4;
	
	if (rows > gDumpLineLimit)
		rows = gDumpLineLimit;
	
	for (uint32 row = 0; row < rows; row++)
		{
		
		printf ("    ");
		
		uint32 col;
		
		uint32 cols = count - (row << 4);
		
		if (cols > 16)
			cols = 16;
		
		uint8 x [16];
		
		for (col = 0; col < 16; col++)
			{
			
			x [col] = ' ';
			
			if (col < cols)
				{
				
				x [col] = stream.Get_uint8 ();
				
				printf ("%02x ", x [col]);
				
				}
				
			else
				{
				printf ("   ");
				}
			
			}
			
		printf ("   ");
		
		for (col = 0; col < 16; col++)
			{
			
			if (x [col] >= (uint8) ' ' && x [col] <= (uint8) '~')
				{
				printf ("%c", x [col]);
				}
				
			else
				{
				printf (".");
				}
				
			}
			
		printf ("\n");
		
		}
		
	if (count > rows * 16)
		{
		printf ("    ... %u more bytes\n", (unsigned) (count - rows * 16));
		}
		
	}
	
/*****************************************************************************/

void DumpHexAscii (const uint8 *buf,
				   uint32 count)
	{
	
	uint32 rows = (count + 15) >> 4;
	
	if (rows > gDumpLineLimit)
		rows = gDumpLineLimit;
	
	for (uint32 row = 0; row < rows; row++)
		{
		
		printf ("    ");
		
		uint32 col;
		
		uint32 cols = count - (row << 4);
		
		if (cols > 16)
			cols = 16;
		
		uint8 x [16];
		
		for (col = 0; col < 16; col++)
			{
			
			x [col] = ' ';
			
			if (col < cols)
				{
				
				x [col] = *(buf++);
				
				printf ("%02x ", x [col]);
				
				}
				
			else
				{
				printf ("   ");
				}
			
			}
			
		printf ("   ");
		
		for (col = 0; col < 16; col++)
			{
			
			if (x [col] >= (uint8) ' ' && x [col] <= (uint8) '~')
				{
				printf ("%c", x [col]);
				}
				
			else
				{
				printf (".");
				}
				
			}
			
		printf ("\n");
		
		}
		
	if (count > rows * 16)
		{
		printf ("    ... %u more bytes\n", (unsigned) (count - rows * 16));
		}
		
	}
	
/*****************************************************************************/

void DumpXMP (dng_stream &stream,
			  uint32 count)
	{
	
	uint32 lineLength = 0;
	
	while (count > 0)
		{
		
		uint32 x = stream.Get_uint8 ();
		
		if (x == 0) break;
		
		count--;
		
		if (lineLength == 0)
			{
			
			printf ("XMP: ");
			
			lineLength = 5;
			
			}
				
		if (x == '\n' ||
			x == '\r')
			{
			
			printf ("\n");
			
			lineLength = 0;
			
			}
			
		else
			{
			
			if (lineLength >= 128)
				{
				
				printf ("\nXMP: ");
				
				lineLength = 5;
				
				}
			
			if (x >= ' ' && x <= '~')
				{
				
				printf ("%c", (char) x);
				
				lineLength += 1;
				
				}
				
			else
				{
				
				printf ("\\%03o", (unsigned) x);
				
				lineLength += 4;
				
				}
				
			}
		
		}
		
	if (lineLength != 0)
		{
		
		printf ("\n");
		
		}
	
	}

/*****************************************************************************/

void DumpString (const dng_string &s)
	{
	
	const uint32 kMaxDumpString = gDumpLineLimit * 64;
	
	printf ("\"");
	
	const char *ss = s.Get ();
	
	uint32 total = 0;
	
	while (*ss != 0 && total++ < kMaxDumpString)
		{
		
		uint32 c = dng_string::DecodeUTF8 (ss);
		
		if (c >= ' ' && c <= '~')
			{
			printf ("%c", (char) c);
			}
			
		else switch (c)
			{
			
			case '\t':
				{
				printf ("\\t");
				break;
				}
			
			case '\n':
				{
				printf ("\\n");
				break;
				}
			
			case '\r':
				{
				printf ("\\r");
				break;
				}
			
			default:
				{
				printf ("[%X]", (unsigned) c);
				}
			
			}
		
		}
	
	uint32 extra = (uint32) strlen (ss);

	if (extra > 0)
		{
		printf ("...\" (%u more bytes)", (unsigned) extra);
		}
	
	else
		{
		printf ("\"");
		}
			
	}

/*****************************************************************************/

void DumpTagValues (dng_stream &stream,
					const char *entry_name,
					uint32 parentCode,
					uint32 tagCode,
					uint32 tagType,
					uint32 tagCount,
					const char *tag_name)
	{
	
	const uint32 kMaxDumpSingleLine = 4;
	
	const uint32 kMaxDumpArray = Max_uint32 (gDumpLineLimit, kMaxDumpSingleLine);
	
	printf ("%s:", tag_name ? tag_name
							: LookupTagCode (parentCode, tagCode));
	
	switch (tagType)
		{
		
		case ttShort:
		case ttLong:
		case ttIFD:
		case ttSByte:
		case ttSShort:
		case ttSLong:
		case ttRational:
		case ttSRational:
		case ttFloat:
		case ttDouble:
			{
			
			if (tagCount > kMaxDumpSingleLine)
				{
				
				printf (" %u entries", (unsigned) tagCount);
				
				}
			
			for (uint32 j = 0; j < tagCount && j < kMaxDumpArray; j++)
				{
				
				if (tagCount <= kMaxDumpSingleLine)
					{
					
					if (j == 0)
						{
						
						printf (" %s =", entry_name);
							
						}
					
					printf (" ");
					
					}
					
				else
					{
					
					printf ("\n    %s [%u] = ", entry_name, (unsigned) j);
					
					}
					
				switch (tagType)
					{
					
					case ttByte:
					case ttShort:
					case ttLong:
					case ttIFD:
						{
				
						uint32 x = stream.TagValue_uint32 (tagType);
						
						printf ("%u", (unsigned) x);
						
						break;
						
						}
						
					case ttSByte:
					case ttSShort:
					case ttSLong:
						{
				
						int32 x = stream.TagValue_int32 (tagType);
						
						printf ("%d", (int) x);
						
						break;
						
						}
						
					case ttRational:
						{
						
						dng_urational x = stream.TagValue_urational (tagType);
						
						printf ("%u/%u", (unsigned) x.n, (unsigned) x.d);
						
						break;
						
						}
						
					case ttSRational:
						{
						
						dng_srational x = stream.TagValue_srational (tagType);
						
						printf ("%d/%d", (int) x.n, (int) x.d);
						
						break;
						
						}
						
					default:
						{
						
						real64 x = stream.TagValue_real64 (tagType);
						
						printf ("%f", x);
						
						}
						
					}
					
				}
				
			printf ("\n");
			
			if (tagCount > kMaxDumpArray)
				{
				
				printf ("    ... %u more entries\n", (unsigned) (tagCount - kMaxDumpArray));
				
				}
				
			break;
				
			}
			
		case ttAscii:
			{
			
			dng_string s;
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							s,
							false);
			
			printf (" ");
			
			DumpString (s);
			
			printf ("\n");
				
			break;
			
			}
		
		default:
			{
			
			uint32 tagSize = tagCount * TagTypeSize (tagType);
			
			if (tagCount == 1 && (tagType == ttByte ||
								  tagType == ttUndefined))
				{
				
				uint8 x = stream.Get_uint8 ();
				
				printf (" %s = %u\n", LookupTagType (tagType), x);
				
				}
				
			else
				{
			
				printf (" %s, size = %u\n", LookupTagType (tagType), (unsigned) tagSize);
						
				DumpHexAscii (stream, tagSize);
				
				}
	
			}
		
		}
	
	}

/*****************************************************************************/

void DumpMatrix (const dng_matrix &m)
	{
	
	for (uint32 row = 0; row < m.Rows (); row++)
		{
		
		for (uint32 col = 0; col < m.Cols (); col++)
			{
			
			if (col == 0)
				printf ("    ");
			else
				printf (" ");
			
			printf ("%8.4f", m [row] [col]);
			
			}
			
		printf ("\n");
		
		}
				
	}

/*****************************************************************************/

void DumpVector (const dng_vector &v)
	{
	
	for (uint32 index = 0; index < v.Count (); index++)
		{
		
		printf (" %0.4f", v [index]);
		
		}

	printf ("\n");
		
	}

/*****************************************************************************/

void DumpDateTime (const dng_date_time &dt)
	{
	
	printf ("%04d:%02d:%02d %02d:%02d:%02d",
			(int) dt.fYear,
			(int) dt.fMonth,
			(int) dt.fDay,
			(int) dt.fHour,
			(int) dt.fMinute,
			(int) dt.fSecond);
	
	}

/*****************************************************************************/

void DumpExposureTime (real64 x)
	{

	if (x > 0.0)
		{
	
		if (x >= 0.25)
			{
			printf ("%0.2f sec", x);
			}
			
		else if (x >= 0.01)
			{
			printf ("1/%0.1f sec", 1.0 / x);
			}
			
		else
			{
			printf ("1/%0.0f sec", 1.0 / x);
			}
			
		}
		
	else
		{
		
		printf ("<invalid>");
		
		}
		
	}

/*****************************************************************************/

void DumpFingerprint (const dng_fingerprint &p)
	{
	
	printf ("<");
	
	for (uint32 j = 0; j < 16; j++)
		{
		printf ("%02x", p.data [j]);
		}

	printf (">");
	
	}

/*****************************************************************************/

void DumpHueSatMap (dng_stream &stream,
				    uint32 hues,
					uint32 sats,
					uint32 vals,
					bool skipSat0)
	{
	
	uint32 doneLines = 0;
	uint32 skipLines = 0;
	
	for (uint32 v = 0; v < vals; v++)
		{
		
		for (uint32 h = 0; h < hues; h++)
			{
			
			for (uint32 s = skipSat0 ? 1 : 0; s < sats; s++)
				{
				
				real32 dh = stream.Get_real32 ();
				real32 ds = stream.Get_real32 ();
				real32 dv = stream.Get_real32 ();
				
				if (gDumpLineLimit == 0 ||
					gDumpLineLimit > doneLines)
					{
					
					doneLines++;
					
					if (vals == 1)
						{
					
						printf ("    h [%2u] s [%2u]:  h=%8.4f s=%6.4f v=%6.4f\n",
								(unsigned) h,
								(unsigned) s,
								(double) dh,
								(double) ds,
								(double) dv);
								
						}
						
					else
						{
					
						printf ("    v [%2u] h [%2u] s [%2u]:  h=%8.4f s=%6.4f v=%6.4f\n",
								(unsigned) v,
								(unsigned) h,
								(unsigned) s,
								(double) dh,
								(double) ds,
								(double) dv);
								
						}
					
					}
					
				else
					{
					
					skipLines++;
					
					}
				
				}
				
			}
			
		}
		
	if (skipLines > 0)
		{
		
		printf ("    ... %u more entries\n", (unsigned) skipLines);
				
		}
	
	}

/*****************************************************************************/

#endif

/*****************************************************************************/

bool CheckTagType (uint32 parentCode,
				   uint32 tagCode,
				   uint32 tagType,
				   uint16 validType0,
				   uint16 validType1,
				   uint16 validType2,
				   uint16 validType3)
	{
	
	if (tagType != validType0 &&
		tagType != validType1 &&
		tagType != validType2 &&
		tagType != validType3)
		{
		
		#if qDNGValidate
		
			{
				
			char message [256];
			
			sprintf (message,
					 "%s %s has unexpected type (%s)",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode),
					 LookupTagType (tagType));
					 
			ReportWarning (message);
						 
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;		// Unused
			
		#endif
			
		return false;
		
		}
		
	return true;
	
	}

/*****************************************************************************/

bool CheckTagCount (uint32 parentCode,
					uint32 tagCode,
				    uint32 tagCount,
				    uint32 minCount,
				    uint32 maxCount)
	{
	
	if (maxCount < minCount)
		maxCount = minCount;
		
	if (tagCount < minCount ||
		tagCount > maxCount)
		{
		
		#if qDNGValidate
		
			{
				
			char message [256];
			
			sprintf (message,
					 "%s %s has unexpected count (%u)",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode),
					 (unsigned) tagCount);
					 
			ReportWarning (message);
						 
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;		// Unused
		
		#endif
			
		return false;
		
		}
		
	return true;
	
	}

/*****************************************************************************/

bool CheckColorImage (uint32 parentCode,
					  uint32 tagCode,
				      uint32 colorPlanes)
	{
	
	if (colorPlanes == 0)
		{
		
		#if qDNGValidate
		
			{
				
			char message [256];
			
			sprintf (message,
					 "%s %s is not allowed with unknown color plane count "
					 " (missing ColorMatrix1 tag?)",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode));
					 
			ReportWarning (message);
						 
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;		// Unused
			
		#endif
			
		return false;
		
		}
	
	if (colorPlanes == 1)
		{
	
		#if qDNGValidate
		
			{
				
			char message [256];
			
			sprintf (message,
					 "%s %s is not allowed with monochrome images",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode));
					 
			ReportWarning (message);
						 
			}
			
		#endif
			
		return false;
		
		}
	
	return true;
	
	}

/*****************************************************************************/

bool CheckMainIFD (uint32 parentCode,
				   uint32 tagCode,
				   uint32 newSubFileType)
	{
	
	if (newSubFileType != sfMainImage)
		{
		
		#if qDNGValidate
		
			{
				
			char message [256];
			
			sprintf (message,
					 "%s %s is not allowed IFDs with NewSubFileType != 0",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode));
					 
			ReportWarning (message);
						 
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;			// Unused
			
		#endif
			
		return false;
		
		}
		
	return true;
	
	}

/*****************************************************************************/

bool CheckRawIFD (uint32 parentCode,
				  uint32 tagCode,
				  uint32 photometricInterpretation)
	{
	
	if (photometricInterpretation != piCFA &&
		photometricInterpretation != piLinearRaw)
		{
		
		#if qDNGValidate
		
			{
				
			char message [256];
			
			sprintf (message,
					 "%s %s is not allowed in IFDs with a non-raw PhotometricInterpretation",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode));
					 
			ReportWarning (message);
						 
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;			// Unused
			
		#endif
			
		return false;
		
		}
		
	return true;
	
	}

/*****************************************************************************/

bool CheckCFA (uint32 parentCode,
			   uint32 tagCode,
		       uint32 photometricInterpretation)
	{
	
	if (photometricInterpretation != piCFA)
		{
		
		#if qDNGValidate

			{
				
			char message [256];
			
			sprintf (message,
					 "%s %s is not allowed in IFDs with a non-CFA PhotometricInterpretation",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode));
					 
			ReportWarning (message);
						 
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;			// Unused
			
		#endif
			
		return false;
		
		}
		
	return true;
	
	}

/*****************************************************************************/

void ParseStringTag (dng_stream &stream,
					 uint32 parentCode,
					 uint32 tagCode,
				     uint32 tagCount,
				     dng_string &s,
				     bool trimBlanks)
	{
	
	if (tagCount == 0 ||
		tagCount == 0xFFFFFFFF)
		{
		
		s.Clear ();
		
		return;
		
		}
	
	dng_memory_data temp_buffer (tagCount + 1);
	
	char *buffer = temp_buffer.Buffer_char ();
	
	stream.Get (buffer, tagCount);
			
	// Make sure the string is null terminated.
	
	if (buffer [tagCount - 1] != 0)
		{
		
		buffer [tagCount] = 0;
		
		#if qDNGValidate
		
			{
		
			bool hasNull = false;
			
			for (uint32 j = 0; j < tagCount; j++)
				{
				
				if (buffer [j] == 0)
					{
					
					hasNull = true;
					
					break;
					
					}
					
				}
				
			if (!hasNull && parentCode < tcFirstMakerNoteIFD)
				{
					
				char message [256];
				
				sprintf (message,
						 "%s %s is not NULL terminated",
						 LookupParentCode (parentCode),
						 LookupTagCode (parentCode, tagCode));
						 
				ReportWarning (message);
							 
				}
				
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;			// Unused
			
		#endif
			
		}
		
	// Medata working group - Allow UTF-8
		
	s.Set_UTF8_or_System (buffer);
				
	if (trimBlanks)
		{
		
		s.TrimTrailingBlanks ();
		
		}
		
	}

/*****************************************************************************/

void ParseDualStringTag (dng_stream &stream,
					 	 uint32 parentCode,
					 	 uint32 tagCode,
				     	 uint32 tagCount,
				     	 dng_string &s1,
				     	 dng_string &s2)
	{
	
	if (tagCount == 0 ||
		tagCount == 0xFFFFFFFF)
		{
		
		s1.Clear ();
		s2.Clear ();
		
		return;
		
		}
	
	dng_memory_data temp_buffer (tagCount + 1);
	
	char *buffer = temp_buffer.Buffer_char ();
	
	stream.Get (buffer, tagCount);
			
	// Make sure the string is null terminated.
	
	if (buffer [tagCount - 1] != 0)
		{
		
		buffer [tagCount] = 0;
		
		#if qDNGValidate
		
			{
			
			uint32 nullCount = 0;
			
			for (uint32 j = 0; j < tagCount; j++)
				{
				
				if (buffer [j] == 0)
					{
					
					nullCount++;
					
					}
					
				}
	
			if (nullCount < 2 && parentCode < tcFirstMakerNoteIFD)
				{
					
				char message [256];
				
				sprintf (message,
						 "%s %s is not NULL terminated",
						 LookupParentCode (parentCode),
						 LookupTagCode (parentCode, tagCode));
						 
				ReportWarning (message);
							 
				}
				
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;			// Unused
			
		#endif
			
		}
		
	// Medata working group - Allow UTF-8
		
	s1.Set_UTF8_or_System (buffer);
		
	s2.Set_ASCII (NULL);
				
	for (uint32 j = 1; j < tagCount - 1; j++)
		{
		
		if (buffer [j - 1] != 0 &&
			buffer [j    ] == 0)
			{
			
			// Medata working group - Allow UTF-8
		
			s2.Set_UTF8_or_System (buffer + j + 1);

			break;
			
			}
		
		}
		
	s1.TrimTrailingBlanks ();
	s2.TrimTrailingBlanks ();
		
	}

/*****************************************************************************/

void ParseEncodedStringTag (dng_stream &stream,
							uint32 parentCode,
							uint32 tagCode,
				    		uint32 tagCount,
				    		dng_string &s)
	{
	
	if (tagCount < 8)
		{
		
		#if qDNGValidate
		
			{
				
			char message [256];
			
			sprintf (message,
					 "%s %s has unexpected count (%u)",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode),
					 (unsigned) tagCount);
					 
			ReportWarning (message);
						 
			}
			
		#else
		
		(void) parentCode;		// Unused
		(void) tagCode;			// Unused
			
		#endif
			
		s.Clear ();
			
		return;

		}
		
	char label [8];
	
	stream.Get (label, 8);
	
	// Sometimes lowercase is used by mistake.  Accept this, but issue
	// warning.
	
		{
		
		bool hadLower = false;
		
		for (uint32 j = 0; j < 8; j++)
			{
			
			if (label [j] >= 'a' && label [j] <= 'z')
				{
				
				label [j] = 'A' + (label [j] - 'a');
				
				hadLower = true;
				
				}
				
			}
			
		#if qDNGValidate
		
		if (hadLower)
			{

			char message [256];
			
			sprintf (message,
					 "%s %s text encoding label not all uppercase",
					 LookupParentCode (parentCode),
					 LookupTagCode (parentCode, tagCode));
					 
			ReportWarning (message);
						 
			}
		 
		#endif
		
		}
	
	if (memcmp (label, "UNICODE\000", 8) == 0)
		{
		
		uint32 uChars = (tagCount - 8) >> 1;
		
		dng_memory_data temp_buffer ((uChars + 1) * 2);
		
		uint16 *buffer = temp_buffer.Buffer_uint16 ();
		
		for (uint32 j = 0; j < uChars; j++)
			{
			
			buffer [j] = stream.Get_uint16 ();
			
			}
			
		buffer [uChars] = 0;
		
		#if qDNGValidate
		
			{
			
			// If the writer used UTF-8 rather than UTF-16, and padded
			// the string with blanks, then there will be lots of 0x2020
			// (unicode dagger symbol) characters in the string.
		
			uint32 count2020 = 0;
			
			for (uint32 k = 0; buffer [k] != 0; k++)
				{
				
				if (buffer [k] == 0x2020)
					{
					
					count2020++;
					
					}
					
				}
				
			if (count2020 > 1)
				{
				
				char message [256];
				
				sprintf (message,
						 "%s %s text appears to be UTF-8 rather than UTF-16",
						 LookupParentCode (parentCode),
						 LookupTagCode (parentCode, tagCode));
						 
				ReportWarning (message);

				}
				
			}
		
		#endif
		
		s.Set_UTF16 (buffer);
		
		}
		
	else
		{
	
		uint32 aChars = tagCount - 8;
		
		dng_memory_data temp_buffer (aChars + 1);
		
		char *buffer = temp_buffer.Buffer_char ();
		
		stream.Get (buffer, aChars);
		
		buffer [aChars] = 0;
		
		enum dng_encoding
			{
			dng_encoding_ascii,
			dng_encoding_jis_x208_1990,
			dng_encoding_unknown
			};
	
		dng_encoding encoding = dng_encoding_unknown;
		
		if (memcmp (label, "ASCII\000\000\000", 8) == 0)
			{
			
			encoding = dng_encoding_ascii;
			
			}
			
		else if (memcmp (label, "JIS\000\000\000\000\000\000", 8) == 0)
			{
			
			encoding = dng_encoding_jis_x208_1990;
			
			}
			
		else
			{
			
			// Some Nikon D1 files have UserComment tags with zero encoding bits and
			// garbage text values.  So don't try to parse tags with unknown text
			// encoding unless all the characters are printing ASCII.
			
			#if qDNGValidate
				
			if (memcmp (label, "\000\000\000\000\000\000\000\000\000", 8) == 0)
				{
				
				// Many camera makes store null tags with all zero encoding, so
				// don't report a warning message for null strings.
				
				if (buffer [0] != 0)
					{
					
					char message [256];
					
					sprintf (message,
							 "%s %s has unknown encoding",
							 LookupParentCode (parentCode),
							 LookupTagCode (parentCode, tagCode));
							 
					ReportWarning (message);
					
					}
							 
				}
				
			else
				{
					
				char message [256];
				
				sprintf (message,
						 "%s %s has unexpected text encoding",
						 LookupParentCode (parentCode),
						 LookupTagCode (parentCode, tagCode));
						 
				ReportWarning (message);
							 
				}
				
			#endif
			
			}
		
		// If text encoding was unknown, and the text is anything
		// other than pure ASCII, then ignore it.
		
		if (encoding == dng_encoding_unknown)
			{
			
			encoding = dng_encoding_ascii;
			
			for (uint32 i = 0; i < aChars && buffer [i] != 0; i++)
				{
				
				if (buffer [i] < ' ' ||
					buffer [i] > '~')
					{
					
					buffer [0] = 0;
					
					break;
					
					}
				
				}
			
			}
			
		switch (encoding)
			{
			
			case dng_encoding_ascii:
				{
				
				// Medata working group - allow UTF-8 for ASCII tags.
				
				s.Set_UTF8_or_System (buffer);
				
				break;
				
				}
				
			case dng_encoding_jis_x208_1990:
				{
				s.Set_JIS_X208_1990 (buffer);
				break;
				}
				
			case dng_encoding_unknown:
				{
				s.Set_SystemEncoding (buffer);
				break;
				}
				
			default:
				break;
				
			}
		
		#if qDNGValidate
		
			{
			
			if (encoding == dng_encoding_ascii && !s.IsASCII ())
				{
			
				char message [256];
				
				sprintf (message,
						 "%s %s has non-ASCII characters",
						 LookupParentCode (parentCode),
						 LookupTagCode (parentCode, tagCode));
						 
				ReportWarning (message);
							 
				}
			
			}
			
		#endif
		
		}
		
	s.TrimTrailingBlanks ();
		
	}
				    		
/*****************************************************************************/

bool ParseMatrixTag (dng_stream &stream,
					 uint32 parentCode,
					 uint32 tagCode,
					 uint32 tagType,
					 uint32 tagCount,
					 uint32 rows,
					 uint32 cols,
					 dng_matrix &m)
	{
	
	if (CheckTagCount (parentCode, tagCode, tagCount, rows * cols))
		{
		
		dng_matrix temp (rows, cols);
		
		for (uint32 row = 0; row < rows; row++)
			for (uint32 col = 0; col < cols; col++)
				{
				
				temp [row] [col] = stream.TagValue_real64 (tagType);
				
				}
				
		m = temp;
		
		return true;
		
		}
		
	return false;
	
	}
				    		
/*****************************************************************************/

bool ParseVectorTag (dng_stream &stream,
					 uint32 parentCode,
					 uint32 tagCode,
					 uint32 tagType,
					 uint32 tagCount,
					 uint32 count,
					 dng_vector &v)
	{
	
	if (CheckTagCount (parentCode, tagCode, tagCount, count))
		{
		
		dng_vector temp (count);
		
		for (uint32 index = 0; index < count; index++)
			{
			
			temp [index] = stream.TagValue_real64 (tagType);
			
			}
				
		v = temp;
		
		return true;
		
		}
		
	return false;
	
	}
				    		
/*****************************************************************************/

bool ParseDateTimeTag (dng_stream &stream,
					   uint32 parentCode,
					   uint32 tagCode,
					   uint32 tagType,
					   uint32 tagCount,
					   dng_date_time &dt)
	{
	
	if (!CheckTagType (parentCode, tagCode, tagType, ttAscii))
		{
		return false;
		}
		
	// Kludge: Some versions of PaintShop Pro write these fields
	// with a length of 21 rather than 20.  Otherwise they are
	// correctly formated.  So relax this test and allow these
	// these longer than standard tags to be parsed.
		
	(void) CheckTagCount (parentCode, tagCode, tagCount, 20);
		
	if (tagCount < 20)
		{
		return false;
		}
			
	char s [21];
	
	stream.Get (s, 20);
	
	s [20] = 0;
	
	// See if this is a valid date/time string.
	
	if (dt.Parse (s))
		{
		return true;
		}

	// Accept strings that contain only blanks, colons, and zeros as
	// valid "null" dates.
	
	dt = dng_date_time ();
	
	for (uint32 index = 0; index < 21; index++)
		{
		
		char c = s [index];
		
		if (c == 0)
			{
			return true;
			}
			
		if (c != ' ' && c != ':' && c != '0')
			{
			
			#if qDNGValidate
			
				{
			
				char message [256];
				
				sprintf (message,
						 "%s %s is not a valid date/time",
						 LookupParentCode (parentCode),
						 LookupTagCode (parentCode, tagCode));
						 
				ReportWarning (message);
							 
				}
				
			#endif
			
			return false;
			
			}
		
		}
	
	return false;
	
	}
				    		
/*****************************************************************************/