diff --git a/image/parsers/png.js b/image/parsers/png.js index 8a9d373..f378af8 100644 --- a/image/parsers/png.js +++ b/image/parsers/png.js @@ -14,7 +14,7 @@ import { ByteStream } from '../../io/bytestream.js'; // https://www.w3.org/TR/png-3/ // https://en.wikipedia.org/wiki/PNG#File_format -// TODO: Ancillary chunks eXIf, hIST, pHYs, sPLT, tIME. +// TODO: Ancillary chunks eXIf, hIST, pHYs, sPLT. // let DEBUG = true; let DEBUG = false; @@ -34,6 +34,7 @@ export const PngParseEventType = { iTXt: 'intl_text_data', sBIT: 'significant_bits', tEXt: 'textual_data', + tIME: 'last_mod_time', tRNS: 'transparency', zTXt: 'compressed_textual_data', }; @@ -242,6 +243,25 @@ export class PngBackgroundColorEvent extends Event { } } +/** + * @typedef PngLastModTime + * @property {number} year Four-digit year. + * @property {number} month One-based. Value from 1-12. + * @property {number} day One-based. Value from 1-31. + * @property {number} hour Zero-based. Value from 0-23. + * @property {number} minute Zero-based. Value from 0-59. + * @property {number} second Zero-based. Value from 0-60 to allow for leap-seconds. + */ + +export class PngLastModTimeEvent extends Event { + /** @param {PngLastModTime} lastModTime */ + constructor(lastModTime) { + super(PngParseEventType.tIME); + /** @type {PngLastModTime} */ + this.lastModTime = lastModTime; + } +} + /** * @typedef PngChunk Internal use only. * @property {number} length @@ -347,6 +367,16 @@ export class PngParser extends EventTarget { return this; } + /** + * Type-safe way to bind a listener for a PngLastModTime. + * @param {function(PngLastModTime): void} listener + * @returns {PngParser} for chaining + */ + onLastModTime(listener) { + super.addEventListener(PngParseEventType.tIME, listener); + return this; + } + /** * Type-safe way to bind a listener for a PngPaletteEvent. * @param {function(PngPaletteEvent): void} listener @@ -553,6 +583,20 @@ export class PngParser extends EventTarget { this.dispatchEvent(new PngTextualDataEvent(textualData)); break; + // https://www.w3.org/TR/png-3/#11tIME + case 'tIME': + /** @type {PngLastModTime} */ + const lastModTime = { + year: chStream.readNumber(2), + month: chStream.readNumber(1), + day: chStream.readNumber(1), + hour: chStream.readNumber(1), + minute: chStream.readNumber(1), + second: chStream.readNumber(1), + }; + this.dispatchEvent(new PngLastModTimeEvent(lastModTime)); + break; + // https://www.w3.org/TR/png-3/#11tRNS case 'tRNS': if (this.colorType === undefined) throw `tRNS before IHDR`; @@ -704,7 +748,10 @@ async function main() { }); parser.onBackgroundColor(evt => { // console.dir(evt.backgroundColor); - }) + }); + parser.onLastModTime(evt => { + console.dir(evt.lastModTime); + }); try { await parser.start(); @@ -714,4 +761,4 @@ async function main() { } } -// main(); \ No newline at end of file +// main(); diff --git a/tests/image-parsers-png.spec.js b/tests/image-parsers-png.spec.js index d3b585b..c20def3 100644 --- a/tests/image-parsers-png.spec.js +++ b/tests/image-parsers-png.spec.js @@ -10,6 +10,7 @@ import { PngColorType, PngInterlaceMethod, PngParser } from '../image/parsers/pn /** @typedef {import('../image/parsers/png.js').PngImageGamma} PngImageGamma */ /** @typedef {import('../image/parsers/png.js').PngImageHeader} PngImageHeader */ /** @typedef {import('../image/parsers/png.js').PngIntlTextualData} PngIntlTextualData */ +/** @typedef {import('../image/parsers/png.js').PngLastModTime} PngLastModTime */ /** @typedef {import('../image/parsers/png.js').PngPalette} PngPalette */ /** @typedef {import('../image/parsers/png.js').PngSignificantBits} PngSignificantBits */ /** @typedef {import('../image/parsers/png.js').PngTextualData} PngTextualData */ @@ -233,4 +234,18 @@ describe('bitjs.image.parsers.PngParser', () => { expect(bc.paletteIndex).equals(245); }); }); + + it('extracts tIME', async () => { + /** @type {PngLastModTime} */ + let lastModTime; + await getPngParser('tests/image-testfiles/cm9n0g04.png') + .onLastModTime(evt => { lastModTime = evt.lastModTime }) + .start(); + expect(lastModTime.year).equals(1999); + expect(lastModTime.month).equals(12); + expect(lastModTime.day).equals(31); + expect(lastModTime.hour).equals(23); + expect(lastModTime.minute).equals(59); + expect(lastModTime.second).equals(59); + }); }); diff --git a/tests/image-testfiles/cm9n0g04.png b/tests/image-testfiles/cm9n0g04.png new file mode 100644 index 0000000..dd70911 Binary files /dev/null and b/tests/image-testfiles/cm9n0g04.png differ