From c1f9b1f7b1b77776192048005dcc66dcf3df2bfb Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 27 Dec 2014 15:41:37 +0100 Subject: Update to MediaWiki 1.24.1 --- .../src/mediawiki.libs/CLDRPluralRuleParser.js | 475 +++++++++++++ .../src/mediawiki.libs/mediawiki.libs.jpegmeta.js | 737 +++++++++++++++++++++ 2 files changed, 1212 insertions(+) create mode 100644 resources/src/mediawiki.libs/CLDRPluralRuleParser.js create mode 100644 resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js (limited to 'resources/src/mediawiki.libs') diff --git a/resources/src/mediawiki.libs/CLDRPluralRuleParser.js b/resources/src/mediawiki.libs/CLDRPluralRuleParser.js new file mode 100644 index 00000000..83c25245 --- /dev/null +++ b/resources/src/mediawiki.libs/CLDRPluralRuleParser.js @@ -0,0 +1,475 @@ +/* This is CLDRPluralRuleParser v1.1, ported to MediaWiki ResourceLoader */ + +/** +* CLDRPluralRuleParser.js +* A parser engine for CLDR plural rules. +* +* Copyright 2012 GPLV3+, Santhosh Thottingal +* +* @version 0.1.0-alpha +* @source https://github.com/santhoshtr/CLDRPluralRuleParser +* @author Santhosh Thottingal +* @author Timo Tijhof +* @author Amir Aharoni +*/ + +( function ( mw ) { +/** + * Evaluates a plural rule in CLDR syntax for a number + * @param {string} rule + * @param {integer} number + * @return {boolean} true if evaluation passed, false if evaluation failed. + */ + +function pluralRuleParser(rule, number) { + /* + Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules + ----------------------------------------------------------------- + condition = and_condition ('or' and_condition)* + ('@integer' samples)? + ('@decimal' samples)? + and_condition = relation ('and' relation)* + relation = is_relation | in_relation | within_relation + is_relation = expr 'is' ('not')? value + in_relation = expr (('not')? 'in' | '=' | '!=') range_list + within_relation = expr ('not')? 'within' range_list + expr = operand (('mod' | '%') value)? + operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w' + range_list = (range | value) (',' range_list)* + value = digit+ + digit = 0|1|2|3|4|5|6|7|8|9 + range = value'..'value + samples = sampleRange (',' sampleRange)* (',' ('…'|'...'))? + sampleRange = decimalValue '~' decimalValue + decimalValue = value ('.' value)? + */ + + // we don't evaluate the samples section of the rule. Ignore it. + rule = rule.split('@')[0].replace(/^\s*/, '').replace(/\s*$/, ''); + + if (!rule.length) { + // empty rule or 'other' rule. + return true; + } + // Indicates current position in the rule as we parse through it. + // Shared among all parsing functions below. + var pos = 0, + operand, + expression, + relation, + result, + whitespace = makeRegexParser(/^\s+/), + value = makeRegexParser(/^\d+/), + _n_ = makeStringParser('n'), + _i_ = makeStringParser('i'), + _f_ = makeStringParser('f'), + _t_ = makeStringParser('t'), + _v_ = makeStringParser('v'), + _w_ = makeStringParser('w'), + _is_ = makeStringParser('is'), + _isnot_ = makeStringParser('is not'), + _isnot_sign_ = makeStringParser('!='), + _equal_ = makeStringParser('='), + _mod_ = makeStringParser('mod'), + _percent_ = makeStringParser('%'), + _not_ = makeStringParser('not'), + _in_ = makeStringParser('in'), + _within_ = makeStringParser('within'), + _range_ = makeStringParser('..'), + _comma_ = makeStringParser(','), + _or_ = makeStringParser('or'), + _and_ = makeStringParser('and'); + + function debug() { + // console.log.apply(console, arguments); + } + + debug('pluralRuleParser', rule, number); + + // Try parsers until one works, if none work return null + + function choice(parserSyntax) { + return function() { + for (var i = 0; i < parserSyntax.length; i++) { + var result = parserSyntax[i](); + if (result !== null) { + return result; + } + } + return null; + }; + } + + // Try several parserSyntax-es in a row. + // All must succeed; otherwise, return null. + // This is the only eager one. + + function sequence(parserSyntax) { + var originalPos = pos; + var result = []; + for (var i = 0; i < parserSyntax.length; i++) { + var res = parserSyntax[i](); + if (res === null) { + pos = originalPos; + return null; + } + result.push(res); + } + return result; + } + + // Run the same parser over and over until it fails. + // Must succeed a minimum of n times; otherwise, return null. + + function nOrMore(n, p) { + return function() { + var originalPos = pos; + var result = []; + var parsed = p(); + while (parsed !== null) { + result.push(parsed); + parsed = p(); + } + if (result.length < n) { + pos = originalPos; + return null; + } + return result; + }; + } + + // Helpers -- just make parserSyntax out of simpler JS builtin types + function makeStringParser(s) { + var len = s.length; + return function() { + var result = null; + if (rule.substr(pos, len) === s) { + result = s; + pos += len; + } + + return result; + }; + } + + function makeRegexParser(regex) { + return function() { + var matches = rule.substr(pos).match(regex); + if (matches === null) { + return null; + } + pos += matches[0].length; + return matches[0]; + }; + } + + /* + * integer digits of n. + */ + function i() { + var result = _i_(); + if (result === null) { + debug(' -- failed i', parseInt(number, 10)); + return result; + } + result = parseInt(number, 10); + debug(' -- passed i ', result); + return result; + } + + /* + * absolute value of the source number (integer and decimals). + */ + function n() { + var result = _n_(); + if (result === null) { + debug(' -- failed n ', number); + return result; + } + result = parseFloat(number, 10); + debug(' -- passed n ', result); + return result; + } + + /* + * visible fractional digits in n, with trailing zeros. + */ + function f() { + var result = _f_(); + if (result === null) { + debug(' -- failed f ', number); + return result; + } + result = (number + '.').split('.')[1] || 0; + debug(' -- passed f ', result); + return result; + } + + /* + * visible fractional digits in n, without trailing zeros. + */ + function t() { + var result = _t_(); + if (result === null) { + debug(' -- failed t ', number); + return result; + } + result = (number + '.').split('.')[1].replace(/0$/, '') || 0; + debug(' -- passed t ', result); + return result; + } + + /* + * number of visible fraction digits in n, with trailing zeros. + */ + function v() { + var result = _v_(); + if (result === null) { + debug(' -- failed v ', number); + return result; + } + result = (number + '.').split('.')[1].length || 0; + debug(' -- passed v ', result); + return result; + } + + /* + * number of visible fraction digits in n, without trailing zeros. + */ + function w() { + var result = _w_(); + if (result === null) { + debug(' -- failed w ', number); + return result; + } + result = (number + '.').split('.')[1].replace(/0$/, '').length || 0; + debug(' -- passed w ', result); + return result; + } + + // operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w' + operand = choice([n, i, f, t, v, w]); + + // expr = operand (('mod' | '%') value)? + expression = choice([mod, operand]); + + function mod() { + var result = sequence([operand, whitespace, choice([_mod_, _percent_]), whitespace, value]); + if (result === null) { + debug(' -- failed mod'); + return null; + } + debug(' -- passed ' + parseInt(result[0], 10) + ' ' + result[2] + ' ' + parseInt(result[4], 10)); + return parseInt(result[0], 10) % parseInt(result[4], 10); + } + + function not() { + var result = sequence([whitespace, _not_]); + if (result === null) { + debug(' -- failed not'); + return null; + } + + return result[1]; + } + + // is_relation = expr 'is' ('not')? value + function is() { + var result = sequence([expression, whitespace, choice([_is_]), whitespace, value]); + if (result !== null) { + debug(' -- passed is : ' + result[0] + ' == ' + parseInt(result[4], 10)); + return result[0] === parseInt(result[4], 10); + } + debug(' -- failed is'); + return null; + } + + // is_relation = expr 'is' ('not')? value + function isnot() { + var result = sequence([expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]); + if (result !== null) { + debug(' -- passed isnot: ' + result[0] + ' != ' + parseInt(result[4], 10)); + return result[0] !== parseInt(result[4], 10); + } + debug(' -- failed isnot'); + return null; + } + + function not_in() { + var result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]); + if (result !== null) { + debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]); + var range_list = result[4]; + for (var i = 0; i < range_list.length; i++) { + if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) { + return false; + } + } + return true; + } + debug(' -- failed not_in'); + return null; + } + + // range_list = (range | value) (',' range_list)* + function rangeList() { + var result = sequence([choice([range, value]), nOrMore(0, rangeTail)]); + var resultList = []; + if (result !== null) { + resultList = resultList.concat(result[0]); + if (result[1][0]) { + resultList = resultList.concat(result[1][0]); + } + return resultList; + } + debug(' -- failed rangeList'); + return null; + } + + function rangeTail() { + // ',' range_list + var result = sequence([_comma_, rangeList]); + if (result !== null) { + return result[1]; + } + debug(' -- failed rangeTail'); + return null; + } + + // range = value'..'value + + function range() { + var i; + var result = sequence([value, _range_, value]); + if (result !== null) { + debug(' -- passed range'); + var array = []; + var left = parseInt(result[0], 10); + var right = parseInt(result[2], 10); + for (i = left; i <= right; i++) { + array.push(i); + } + return array; + } + debug(' -- failed range'); + return null; + } + + function _in() { + // in_relation = expr ('not')? 'in' range_list + var result = sequence([expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]); + if (result !== null) { + debug(' -- passed _in:' + result); + var range_list = result[5]; + for (var i = 0; i < range_list.length; i++) { + if (parseInt(range_list[i], 10) === parseInt(result[0], 10)) { + return (result[1][0] !== 'not'); + } + } + return (result[1][0] === 'not'); + } + debug(' -- failed _in '); + return null; + } + + /* + * The difference between in and within is that in only includes integers in the specified range, + * while within includes all values. + */ + + function within() { + // within_relation = expr ('not')? 'within' range_list + var result = sequence([expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]); + if (result !== null) { + debug(' -- passed within'); + var range_list = result[5]; + if ((result[0] >= parseInt(range_list[0], 10)) && + (result[0] < parseInt(range_list[range_list.length - 1], 10))) { + return (result[1][0] !== 'not'); + } + return (result[1][0] === 'not'); + } + debug(' -- failed within '); + return null; + } + + // relation = is_relation | in_relation | within_relation + relation = choice([is, not_in, isnot, _in, within]); + + // and_condition = relation ('and' relation)* + function and() { + var result = sequence([relation, nOrMore(0, andTail)]); + if (result) { + if (!result[0]) { + return false; + } + for (var i = 0; i < result[1].length; i++) { + if (!result[1][i]) { + return false; + } + } + return true; + } + debug(' -- failed and'); + return null; + } + + // ('and' relation)* + function andTail() { + var result = sequence([whitespace, _and_, whitespace, relation]); + if (result !== null) { + debug(' -- passed andTail' + result); + return result[3]; + } + debug(' -- failed andTail'); + return null; + + } + // ('or' and_condition)* + function orTail() { + var result = sequence([whitespace, _or_, whitespace, and]); + if (result !== null) { + debug(' -- passed orTail: ' + result[3]); + return result[3]; + } + debug(' -- failed orTail'); + return null; + + } + + // condition = and_condition ('or' and_condition)* + function condition() { + var result = sequence([and, nOrMore(0, orTail)]); + if (result) { + for (var i = 0; i < result[1].length; i++) { + if (result[1][i]) { + return true; + } + } + return result[0]; + + } + return false; + } + + result = condition(); + /* + * For success, the pos must have gotten to the end of the rule + * and returned a non-null. + * n.b. This is part of language infrastructure, so we do not throw an internationalizable message. + */ + if (result === null) { + throw new Error('Parse error at position ' + pos.toString() + ' for rule: ' + rule); + } + + if (pos !== rule.length) { + debug('Warning: Rule not parsed completely. Parser stopped at ' + rule.substr(0, pos) + ' for rule: ' + rule); + } + + return result; +} + +/* pluralRuleParser ends here */ +mw.libs.pluralRuleParser = pluralRuleParser; + +} )( mediaWiki ); diff --git a/resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js b/resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js new file mode 100644 index 00000000..b3ed88c8 --- /dev/null +++ b/resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js @@ -0,0 +1,737 @@ +/** + * This is JsJpegMeta v1.0 + * From: https://code.google.com/p/jsjpegmeta/downloads/list + * From: https://github.com/bennoleslie/jsjpegmeta/blob/v1.0.0/jpegmeta.js + * + * Ported to MediaWiki ResourceLoader by Bryan Tong Minh + * Changes: + * - Add closure. + * - Add this.JpegMeta assignment to expose it as global. + * - Add mw.libs.jpegmeta wrapper. + */ + +( function () { + /* + Copyright (c) 2009 Ben Leslie + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + + /* + This JavaScript library is used to parse meta-data from files + with mime-type image/jpeg. + + Include it with something like: + + + + This adds a single 'module' object called 'JpegMeta' to the global + namespace. + + Public Functions + ---------------- + JpegMeta.parseNum - parse unsigned integers from binary data + JpegMeta.parseSnum - parse signed integers from binary data + + Public Classes + -------------- + JpegMeta.Rational - A rational number class + JpegMeta.JfifSegment + JpegMeta.ExifSegment + JpegMeta.JpegFile - Primary class for Javascript parsing + */ + + var JpegMeta = {}; + // MediaWiki: Expose as global + this.JpegMeta = JpegMeta; + + /* + parse an unsigned number of size bytes at offset in some binary string data. + If endian + is "<" parse the data as little endian, if endian + is ">" parse as big-endian. + */ + JpegMeta.parseNum = function parseNum(endian, data, offset, size) { + var i; + var ret; + var big_endian = (endian === ">"); + if (offset === undefined) offset = 0; + if (size === undefined) size = data.length - offset; + for (big_endian ? i = offset : i = offset + size - 1; + big_endian ? i < offset + size : i >= offset; + big_endian ? i++ : i--) { + ret <<= 8; + ret += data.charCodeAt(i); + } + return ret; + }; + + /* + parse an signed number of size bytes at offset in some binary string data. + If endian + is "<" parse the data as little endian, if endian + is ">" parse as big-endian. + */ + JpegMeta.parseSnum = function parseSnum(endian, data, offset, size) { + var i; + var ret; + var neg; + var big_endian = (endian === ">"); + if (offset === undefined) offset = 0; + if (size === undefined) size = data.length - offset; + for (big_endian ? i = offset : i = offset + size - 1; + big_endian ? i < offset + size : i >= offset; + big_endian ? i++ : i--) { + if (neg === undefined) { + /* Negative if top bit is set */ + neg = (data.charCodeAt(i) & 0x80) === 0x80; + } + ret <<= 8; + /* If it is negative we invert the bits */ + ret += neg ? ~data.charCodeAt(i) & 0xff: data.charCodeAt(i); + } + if (neg) { + /* If it is negative we do two's complement */ + ret += 1; + ret *= -1; + } + return ret; + }; + + /* Rational number class */ + JpegMeta.Rational = function Rational(num, den) + { + this.num = num; + this.den = den || 1; + return this; + }; + + /* Rational number methods */ + JpegMeta.Rational.prototype.toString = function toString() { + if (this.num === 0) { + return "" + this.num; + } + if (this.den === 1) { + return "" + this.num; + } + if (this.num === 1) { + return this.num + " / " + this.den; + } + return this.num / this.den; // + "/" + this.den; + }; + + JpegMeta.Rational.prototype.asFloat = function asFloat() { + return this.num / this.den; + }; + + /* MetaGroup class */ + JpegMeta.MetaGroup = function MetaGroup(fieldName, description) { + this.fieldName = fieldName; + this.description = description; + this.metaProps = {}; + return this; + }; + + JpegMeta.MetaGroup.prototype._addProperty = function _addProperty(fieldName, description, value) { + var property = new JpegMeta.MetaProp(fieldName, description, value); + this[property.fieldName] = property; + this.metaProps[property.fieldName] = property; + }; + + JpegMeta.MetaGroup.prototype.toString = function toString() { + return "[MetaGroup " + this.description + "]"; + }; + + /* MetaProp class */ + JpegMeta.MetaProp = function MetaProp(fieldName, description, value) { + this.fieldName = fieldName; + this.description = description; + this.value = value; + return this; + }; + + JpegMeta.MetaProp.prototype.toString = function toString() { + return "" + this.value; + }; + + /* JpegFile class */ + JpegMeta.JpegFile = function JpegFile(binary_data, filename) { + /* Change this to EOI if we want to parse. */ + var break_segment = this._SOS; + + this.metaGroups = {}; + this._binary_data = binary_data; + this.filename = filename; + + /* Go through and parse. */ + var pos = 0; + var pos_start_of_segment = 0; + var delim; + var mark; + var _mark; + var segsize; + var headersize; + var mark_code; + var mark_fn; + + /* Check to see if this looks like a JPEG file */ + if (this._binary_data.slice(0, 2) !== this._SOI_MARKER) { + throw new Error("Doesn't look like a JPEG file. First two bytes are " + + this._binary_data.charCodeAt(0) + "," + + this._binary_data.charCodeAt(1) + "."); + } + + pos += 2; + + while (pos < this._binary_data.length) { + delim = this._binary_data.charCodeAt(pos++); + mark = this._binary_data.charCodeAt(pos++); + + pos_start_of_segment = pos; + + if (delim != this._DELIM) { + break; + } + + if (mark === break_segment) { + break; + } + + headersize = JpegMeta.parseNum(">", this._binary_data, pos, 2); + + /* Find the end */ + pos += headersize; + while (pos < this._binary_data.length) { + delim = this._binary_data.charCodeAt(pos++); + if (delim == this._DELIM) { + _mark = this._binary_data.charCodeAt(pos++); + if (_mark != 0x0) { + pos -= 2; + break; + } + } + } + + segsize = pos - pos_start_of_segment; + + if (this._markers[mark]) { + mark_code = this._markers[mark][0]; + mark_fn = this._markers[mark][1]; + } else { + mark_code = "UNKN"; + mark_fn = undefined; + } + + if (mark_fn) { + this[mark_fn](mark, pos_start_of_segment + 2); + } + + } + + if (this.general === undefined) { + throw Error("Invalid JPEG file."); + } + + return this; + }; + + this.JpegMeta.JpegFile.prototype.toString = function () { + return "[JpegFile " + this.filename + " " + + this.general.type + " " + + this.general.pixelWidth + "x" + + this.general.pixelHeight + + " Depth: " + this.general.depth + "]"; + }; + + /* Some useful constants */ + this.JpegMeta.JpegFile.prototype._SOI_MARKER = '\xff\xd8'; + this.JpegMeta.JpegFile.prototype._DELIM = 0xff; + this.JpegMeta.JpegFile.prototype._EOI = 0xd9; + this.JpegMeta.JpegFile.prototype._SOS = 0xda; + + this.JpegMeta.JpegFile.prototype._sofHandler = function _sofHandler (mark, pos) { + if (this.general !== undefined) { + throw Error("Unexpected multiple-frame image"); + } + + this._addMetaGroup("general", "General"); + this.general._addProperty("depth", "Depth", JpegMeta.parseNum(">", this._binary_data, pos, 1)); + this.general._addProperty("pixelHeight", "Pixel Height", JpegMeta.parseNum(">", this._binary_data, pos + 1, 2)); + this.general._addProperty("pixelWidth", "Pixel Width",JpegMeta.parseNum(">", this._binary_data, pos + 3, 2)); + this.general._addProperty("type", "Type", this._markers[mark][2]); + }; + + /* JFIF idents */ + this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00"; + this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00"; + + /* Exif idents */ + this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00"; + + /* TIFF types */ + this.JpegMeta.JpegFile.prototype._types = { + /* The format is identifier : ["type name", type_size_in_bytes ] */ + 1 : ["BYTE", 1], + 2 : ["ASCII", 1], + 3 : ["SHORT", 2], + 4 : ["LONG", 4], + 5 : ["RATIONAL", 8], + 6 : ["SBYTE", 1], + 7 : ["UNDEFINED", 1], + 8 : ["SSHORT", 2], + 9 : ["SLONG", 4], + 10 : ["SRATIONAL", 8], + 11 : ["FLOAT", 4], + 12 : ["DOUBLE", 8] + }; + + this.JpegMeta.JpegFile.prototype._tifftags = { + /* A. Tags relating to image data structure */ + 256 : ["Image width", "ImageWidth"], + 257 : ["Image height", "ImageLength"], + 258 : ["Number of bits per component", "BitsPerSample"], + 259 : ["Compression scheme", "Compression", + {1 : "uncompressed", 6 : "JPEG compression" }], + 262 : ["Pixel composition", "PhotmetricInerpretation", + {2 : "RGB", 6 : "YCbCr"}], + 274 : ["Orientation of image", "Orientation", + /* FIXME: Check the mirror-image / reverse encoding and rotation */ + {1 : "Normal", 2 : "Reverse?", + 3 : "Upside-down", 4 : "Upside-down Reverse", + 5 : "90 degree CW", 6 : "90 degree CW reverse", + 7 : "90 degree CCW", 8 : "90 degree CCW reverse"}], + 277 : ["Number of components", "SamplesPerPixel"], + 284 : ["Image data arrangement", "PlanarConfiguration", + {1 : "chunky format", 2 : "planar format"}], + 530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"], + 531 : ["Y and C positioning", "YCbCrPositioning", + {1 : "centered", 2 : "co-sited"}], + 282 : ["X Resolution", "XResolution"], + 283 : ["Y Resolution", "YResolution"], + 296 : ["Resolution Unit", "ResolutionUnit", + {2 : "inches", 3 : "centimeters"}], + /* B. Tags realting to recording offset */ + 273 : ["Image data location", "StripOffsets"], + 278 : ["Number of rows per strip", "RowsPerStrip"], + 279 : ["Bytes per compressed strip", "StripByteCounts"], + 513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"], + 514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"], + /* C. Tags relating to image data characteristics */ + 301 : ["Transfer function", "TransferFunction"], + 318 : ["White point chromaticity", "WhitePoint"], + 319 : ["Chromaticities of primaries", "PrimaryChromaticities"], + 529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"], + 532 : ["Pair of black and white reference values", "ReferenceBlackWhite"], + /* D. Other tags */ + 306 : ["Date and time", "DateTime"], + 270 : ["Image title", "ImageDescription"], + 271 : ["Make", "Make"], + 272 : ["Model", "Model"], + 305 : ["Software", "Software"], + 315 : ["Person who created the image", "Artist"], + 316 : ["Host Computer", "HostComputer"], + 33432 : ["Copyright holder", "Copyright"], + + 34665 : ["Exif tag", "ExifIfdPointer"], + 34853 : ["GPS tag", "GPSInfoIfdPointer"] + }; + + this.JpegMeta.JpegFile.prototype._exiftags = { + /* Tag Support Levels (2) - 0th IFX Exif Private Tags */ + /* A. Tags Relating to Version */ + 36864 : ["Exif Version", "ExifVersion"], + 40960 : ["FlashPix Version", "FlashpixVersion"], + + /* B. Tag Relating to Image Data Characteristics */ + 40961 : ["Color Space", "ColorSpace"], + + /* C. Tags Relating to Image Configuration */ + 37121 : ["Meaning of each component", "ComponentsConfiguration"], + 37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"], + 40962 : ["Pixel X Dimension", "PixelXDimension"], + 40963 : ["Pixel Y Dimension", "PixelYDimension"], + + /* D. Tags Relating to User Information */ + 37500 : ["Manufacturer notes", "MakerNote"], + 37510 : ["User comments", "UserComment"], + + /* E. Tag Relating to Related File Information */ + 40964 : ["Related audio file", "RelatedSoundFile"], + + /* F. Tags Relating to Date and Time */ + 36867 : ["Date Time Original", "DateTimeOriginal"], + 36868 : ["Date Time Digitized", "DateTimeDigitized"], + 37520 : ["DateTime subseconds", "SubSecTime"], + 37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"], + 37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"], + + /* G. Tags Relating to Picture-Taking Conditions */ + 33434 : ["Exposure time", "ExposureTime"], + 33437 : ["FNumber", "FNumber"], + 34850 : ["Exposure program", "ExposureProgram"], + 34852 : ["Spectral sensitivity", "SpectralSensitivity"], + 34855 : ["ISO Speed Ratings", "ISOSpeedRatings"], + 34856 : ["Optoelectric coefficient", "OECF"], + 37377 : ["Shutter Speed", "ShutterSpeedValue"], + 37378 : ["Aperture Value", "ApertureValue"], + 37379 : ["Brightness", "BrightnessValue"], + 37380 : ["Exposure Bias Value", "ExposureBiasValue"], + 37381 : ["Max Aperture Value", "MaxApertureValue"], + 37382 : ["Subject Distance", "SubjectDistance"], + 37383 : ["Metering Mode", "MeteringMode"], + 37384 : ["Light Source", "LightSource"], + 37385 : ["Flash", "Flash"], + 37386 : ["Focal Length", "FocalLength"], + 37396 : ["Subject Area", "SubjectArea"], + 41483 : ["Flash Energy", "FlashEnergy"], + 41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"], + 41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"], + 41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"], + 41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"], + 41492 : ["Subject Location", "SubjectLocation"], + 41493 : ["Exposure Index", "ExposureIndex"], + 41495 : ["Sensing Method", "SensingMethod"], + 41728 : ["File Source", "FileSource"], + 41729 : ["Scene Type", "SceneType"], + 41730 : ["CFA Pattern", "CFAPattern"], + 41985 : ["Custom Rendered", "CustomRendered"], + 41986 : ["Exposure Mode", "Exposure Mode"], + 41987 : ["White Balance", "WhiteBalance"], + 41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"], + 41990 : ["Scene Capture Type", "SceneCaptureType"], + 41991 : ["Gain Control", "GainControl"], + 41992 : ["Contrast", "Contrast"], + 41993 : ["Saturation", "Saturation"], + 41994 : ["Sharpness", "Sharpness"], + 41995 : ["Device settings description", "DeviceSettingDescription"], + 41996 : ["Subject distance range", "SubjectDistanceRange"], + + /* H. Other Tags */ + 42016 : ["Unique image ID", "ImageUniqueID"], + + 40965 : ["Interoperability tag", "InteroperabilityIFDPointer"] + }; + + this.JpegMeta.JpegFile.prototype._gpstags = { + /* A. Tags Relating to GPS */ + 0 : ["GPS tag version", "GPSVersionID"], + 1 : ["North or South Latitude", "GPSLatitudeRef"], + 2 : ["Latitude", "GPSLatitude"], + 3 : ["East or West Longitude", "GPSLongitudeRef"], + 4 : ["Longitude", "GPSLongitude"], + 5 : ["Altitude reference", "GPSAltitudeRef"], + 6 : ["Altitude", "GPSAltitude"], + 7 : ["GPS time (atomic clock)", "GPSTimeStamp"], + 8 : ["GPS satellites usedd for measurement", "GPSSatellites"], + 9 : ["GPS receiver status", "GPSStatus"], + 10 : ["GPS mesaurement mode", "GPSMeasureMode"], + 11 : ["Measurement precision", "GPSDOP"], + 12 : ["Speed unit", "GPSSpeedRef"], + 13 : ["Speed of GPS receiver", "GPSSpeed"], + 14 : ["Reference for direction of movement", "GPSTrackRef"], + 15 : ["Direction of movement", "GPSTrack"], + 16 : ["Reference for direction of image", "GPSImgDirectionRef"], + 17 : ["Direction of image", "GPSImgDirection"], + 18 : ["Geodetic survey data used", "GPSMapDatum"], + 19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"], + 20 : ["Latitude of destination", "GPSDestLatitude"], + 21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"], + 22 : ["Longitude of destination", "GPSDestLongitude"], + 23 : ["Reference for bearing of destination", "GPSDestBearingRef"], + 24 : ["Bearing of destination", "GPSDestBearing"], + 25 : ["Reference for distance to destination", "GPSDestDistanceRef"], + 26 : ["Distance to destination", "GPSDestDistance"], + 27 : ["Name of GPS processing method", "GPSProcessingMethod"], + 28 : ["Name of GPS area", "GPSAreaInformation"], + 29 : ["GPS Date", "GPSDateStamp"], + 30 : ["GPS differential correction", "GPSDifferential"] + }; + + this.JpegMeta.JpegFile.prototype._markers = { + /* Start Of Frame markers, non-differential, Huffman coding */ + 0xc0: ["SOF0", "_sofHandler", "Baseline DCT"], + 0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"], + 0xc2: ["SOF2", "_sofHandler", "Progressive DCT"], + 0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"], + + /* Start Of Frame markers, differential, Huffman coding */ + 0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"], + 0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"], + 0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"], + + /* Start Of Frame markers, non-differential, arithmetic coding */ + 0xc8: ["JPG", null, "Reserved for JPEG extensions"], + 0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"], + 0xca: ["SOF10", "_sofHandler", "Progressive DCT"], + 0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"], + + /* Start Of Frame markers, differential, arithmetic coding */ + 0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"], + 0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"], + 0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"], + + /* Huffman table specification */ + 0xc4: ["DHT", null, "Define Huffman table(s)"], + 0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"], + + /* Restart interval termination" */ + 0xd0: ["RST0", null, "Restart with modulo 8 count “0”"], + 0xd1: ["RST1", null, "Restart with modulo 8 count “1”"], + 0xd2: ["RST2", null, "Restart with modulo 8 count “2”"], + 0xd3: ["RST3", null, "Restart with modulo 8 count “3”"], + 0xd4: ["RST4", null, "Restart with modulo 8 count “4”"], + 0xd5: ["RST5", null, "Restart with modulo 8 count “5”"], + 0xd6: ["RST6", null, "Restart with modulo 8 count “6”"], + 0xd7: ["RST7", null, "Restart with modulo 8 count “7”"], + + /* Other markers */ + 0xd8: ["SOI", null, "Start of image"], + 0xd9: ["EOI", null, "End of image"], + 0xda: ["SOS", null, "Start of scan"], + 0xdb: ["DQT", null, "Define quantization table(s)"], + 0xdc: ["DNL", null, "Define number of lines"], + 0xdd: ["DRI", null, "Define restart interval"], + 0xde: ["DHP", null, "Define hierarchical progression"], + 0xdf: ["EXP", null, "Expand reference component(s)"], + 0xe0: ["APP0", "_app0Handler", "Reserved for application segments"], + 0xe1: ["APP1", "_app1Handler"], + 0xe2: ["APP2", null], + 0xe3: ["APP3", null], + 0xe4: ["APP4", null], + 0xe5: ["APP5", null], + 0xe6: ["APP6", null], + 0xe7: ["APP7", null], + 0xe8: ["APP8", null], + 0xe9: ["APP9", null], + 0xea: ["APP10", null], + 0xeb: ["APP11", null], + 0xec: ["APP12", null], + 0xed: ["APP13", null], + 0xee: ["APP14", null], + 0xef: ["APP15", null], + 0xf0: ["JPG0", null], /* Reserved for JPEG extensions */ + 0xf1: ["JPG1", null], + 0xf2: ["JPG2", null], + 0xf3: ["JPG3", null], + 0xf4: ["JPG4", null], + 0xf5: ["JPG5", null], + 0xf6: ["JPG6", null], + 0xf7: ["JPG7", null], + 0xf8: ["JPG8", null], + 0xf9: ["JPG9", null], + 0xfa: ["JPG10", null], + 0xfb: ["JPG11", null], + 0xfc: ["JPG12", null], + 0xfd: ["JPG13", null], + 0xfe: ["COM", null], /* Comment */ + + /* Reserved markers */ + 0x01: ["JPG13", null] /* For temporary private use in arithmetic coding */ + /* 02 -> bf are reserverd */ + }; + + /* Private methods */ + this.JpegMeta.JpegFile.prototype._addMetaGroup = function _addMetaGroup(name, description) { + var group = new JpegMeta.MetaGroup(name, description); + this[group.fieldName] = group; + this.metaGroups[group.fieldName] = group; + return group; + }; + + this.JpegMeta.JpegFile.prototype._parseIfd = function _parseIfd(endian, _binary_data, base, ifd_offset, tags, name, description) { + var num_fields = JpegMeta.parseNum(endian, _binary_data, base + ifd_offset, 2); + /* Per tag variables */ + var i, j; + var tag_base; + var tag_field; + var type, type_field, type_size; + var num_values; + var value_offset; + var value; + var _val; + var num; + var den; + + var group; + + group = this._addMetaGroup(name, description); + + for (var i = 0; i < num_fields; i++) { + /* parse the field */ + tag_base = base + ifd_offset + 2 + (i * 12); + tag_field = JpegMeta.parseNum(endian, _binary_data, tag_base, 2); + type_field = JpegMeta.parseNum(endian, _binary_data, tag_base + 2, 2); + num_values = JpegMeta.parseNum(endian, _binary_data, tag_base + 4, 4); + value_offset = JpegMeta.parseNum(endian, _binary_data, tag_base + 8, 4); + if (this._types[type_field] === undefined) { + continue; + } + type = this._types[type_field][0]; + type_size = this._types[type_field][1]; + + if (type_size * num_values <= 4) { + /* Data is in-line */ + value_offset = tag_base + 8; + } else { + value_offset = base + value_offset; + } + + /* Read the value */ + if (type == "UNDEFINED") { + value = _binary_data.slice(value_offset, value_offset + num_values); + } else if (type == "ASCII") { + value = _binary_data.slice(value_offset, value_offset + num_values); + value = value.split('\x00')[0]; + /* strip trail nul */ + } else { + value = new Array(); + for (j = 0; j < num_values; j++, value_offset += type_size) { + if (type == "BYTE" || type == "SHORT" || type == "LONG") { + value.push(JpegMeta.parseNum(endian, _binary_data, value_offset, type_size)); + } + if (type == "SBYTE" || type == "SSHORT" || type == "SLONG") { + value.push(JpegMeta.parseSnum(endian, _binary_data, value_offset, type_size)); + } + if (type == "RATIONAL") { + num = JpegMeta.parseNum(endian, _binary_data, value_offset, 4); + den = JpegMeta.parseNum(endian, _binary_data, value_offset + 4, 4); + value.push(new JpegMeta.Rational(num, den)); + } + if (type == "SRATIONAL") { + num = JpegMeta.parseSnum(endian, _binary_data, value_offset, 4); + den = JpegMeta.parseSnum(endian, _binary_data, value_offset + 4, 4); + value.push(new JpegMeta.Rational(num, den)); + } + value.push(); + } + if (num_values === 1) { + value = value[0]; + } + } + if (tags[tag_field] !== undefined) { + group._addProperty(tags[tag_field][1], tags[tag_field][0], value); + } + } + }; + + this.JpegMeta.JpegFile.prototype._jfifHandler = function _jfifHandler(mark, pos) { + if (this.jfif !== undefined) { + throw Error("Multiple JFIF segments found"); + } + this._addMetaGroup("jfif", "JFIF"); + this.jfif._addProperty("version_major", "Version Major", this._binary_data.charCodeAt(pos + 5)); + this.jfif._addProperty("version_minor", "Version Minor", this._binary_data.charCodeAt(pos + 6)); + this.jfif._addProperty("version", "JFIF Version", this.jfif.version_major.value + "." + this.jfif.version_minor.value); + this.jfif._addProperty("units", "Density Unit", this._binary_data.charCodeAt(pos + 7)); + this.jfif._addProperty("Xdensity", "X density", JpegMeta.parseNum(">", this._binary_data, pos + 8, 2)); + this.jfif._addProperty("Ydensity", "Y Density", JpegMeta.parseNum(">", this._binary_data, pos + 10, 2)); + this.jfif._addProperty("Xthumbnail", "X Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 12, 1)); + this.jfif._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 13, 1)); + }; + + /* Handle app0 segments */ + this.JpegMeta.JpegFile.prototype._app0Handler = function app0Handler(mark, pos) { + var ident = this._binary_data.slice(pos, pos + 5); + if (ident == this._JFIF_IDENT) { + this._jfifHandler(mark, pos); + } else if (ident == this._JFXX_IDENT) { + /* Don't handle JFXX Ident yet */ + } else { + /* Don't know about other idents */ + } + }; + + /* Handle app1 segments */ + this.JpegMeta.JpegFile.prototype._app1Handler = function _app1Handler(mark, pos) { + var ident = this._binary_data.slice(pos, pos + 5); + if (ident == this._EXIF_IDENT) { + this._exifHandler(mark, pos + 6); + } else { + /* Don't know about other idents */ + } + }; + + /* Handle exif segments */ + JpegMeta.JpegFile.prototype._exifHandler = function _exifHandler(mark, pos) { + if (this.exif !== undefined) { + throw new Error("Multiple JFIF segments found"); + } + + /* Parse this TIFF header */ + var endian; + var magic_field; + var ifd_offset; + var primary_ifd, exif_ifd, gps_ifd; + var endian_field = this._binary_data.slice(pos, pos + 2); + + /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */ + if (endian_field === "II") { + endian = "<"; + } else if (endian_field === "MM") { + endian = ">"; + } else { + throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field); + } + + magic_field = JpegMeta.parseNum(endian, this._binary_data, pos + 2, 2); + + if (magic_field !== 42) { + throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field); + } + + ifd_offset = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 4); + + /* Parse 0th IFD */ + this._parseIfd(endian, this._binary_data, pos, ifd_offset, this._tifftags, "tiff", "TIFF"); + + if (this.tiff.ExifIfdPointer) { + this._parseIfd(endian, this._binary_data, pos, this.tiff.ExifIfdPointer.value, this._exiftags, "exif", "Exif"); + } + + if (this.tiff.GPSInfoIfdPointer) { + this._parseIfd(endian, this._binary_data, pos, this.tiff.GPSInfoIfdPointer.value, this._gpstags, "gps", "GPS"); + if (this.gps.GPSLatitude) { + var latitude; + latitude = this.gps.GPSLatitude.value[0].asFloat() + + (1 / 60) * this.gps.GPSLatitude.value[1].asFloat() + + (1 / 3600) * this.gps.GPSLatitude.value[2].asFloat(); + if (this.gps.GPSLatitudeRef.value === "S") { + latitude = -latitude; + } + this.gps._addProperty("latitude", "Dec. Latitude", latitude); + } + if (this.gps.GPSLongitude) { + var longitude; + longitude = this.gps.GPSLongitude.value[0].asFloat() + + (1 / 60) * this.gps.GPSLongitude.value[1].asFloat() + + (1 / 3600) * this.gps.GPSLongitude.value[2].asFloat(); + if (this.gps.GPSLongitudeRef.value === "W") { + longitude = -longitude; + } + this.gps._addProperty("longitude", "Dec. Longitude", longitude); + } + } + }; + + // MediaWiki: Add mw.libs wrapper + mw.libs.jpegmeta = function( fileReaderResult, fileName ) { + return new JpegMeta.JpegFile( fileReaderResult, fileName ); + }; + +}() ); -- cgit v1.2.2