'!resources/lib/**',
'!resources/src/jquery.tipsy/**',
'!resources/src/jquery/jquery.farbtastic.js',
- '!resources/src/mediawiki.libs/**',
+ '!resources/src/mediawiki.libs.jpegmeta/**',
// Third-party code of PHPUnit coverage report
'!tests/coverage/**',
'!vendor/**',
}
/**
+ * Non-static proxy to ::getLogo (for overloading in sub classes or tests).
+ *
+ * @codeCoverageIgnore
* @since 1.31
* @param Config $conf
* @return string|array
"recentchangeslinked-feed": "Related changes",
"recentchangeslinked-toolbox": "Related changes",
"recentchangeslinked-title": "Changes related to \"$1\"",
- "recentchangeslinked-summary": "Enter a page name to see changes on pages linked to or from that page. (To see members of a category, enter Category:Name of category). Changes to pages on [[Special:Watchlist|your Watchlist]] are in <strong>bold</strong>.",
+ "recentchangeslinked-summary": "Enter a page name to see changes on pages linked to or from that page. (To see members of a category, enter {{ns:category}}:Name of category). Changes to pages on [[Special:Watchlist|your Watchlist]] are in <strong>bold</strong>.",
"recentchangeslinked-page": "Page name:",
"recentchangeslinked-to": "Show changes to pages linked to the given page instead",
"recentchanges-page-added-to-category": "[[:$1]] added to category",
'moment' => [
'scripts' => [
// HACK: For some reason if you don't define window.moment first, loading moment fatals
- 'resources/src/moment-global.js',
+ 'resources/src/moment/moment-global.js',
'resources/lib/moment/moment.js',
],
'languageScripts' => [
'de-ch' => 'resources/lib/moment/locale/de-ch.js',
'dv' => 'resources/lib/moment/locale/dv.js',
'el' => 'resources/lib/moment/locale/el.js',
- 'en' => 'resources/src/moment-dmy.js',
+ 'en' => 'resources/src/moment/moment-dmy.js',
'en-au' => 'resources/lib/moment/locale/en-au.js',
'en-ca' => 'resources/lib/moment/locale/en-ca.js',
'en-gb' => 'resources/lib/moment/locale/en-gb.js',
// after locale definitions
'skinScripts' => [
'default' => [
- 'resources/src/moment-locale-overrides.js',
+ 'resources/src/moment/moment-locale-overrides.js',
],
],
'dependencies' => [
],
'mediawiki.libs.pluralruleparser' => [
- 'scripts' => 'resources/src/mediawiki.libs/CLDRPluralRuleParser.js',
+ 'scripts' => [
+ 'resources/lib/CLDRPluralRuleParser/CLDRPluralRuleParser.js',
+ 'resources/src/mediawiki.libs.pluralruleparser/export.js',
+ ],
'targets' => [ 'desktop', 'mobile' ],
],
/* MediaWiki Libs */
'mediawiki.libs.jpegmeta' => [
- 'scripts' => 'resources/src/mediawiki.libs/mediawiki.libs.jpegmeta.js',
+ 'scripts' => [
+ 'resources/src/mediawiki.libs.jpegmeta/jpegmeta.js',
+ 'resources/src/mediawiki.libs.jpegmeta/export.js',
+ ],
'targets' => [ 'desktop', 'mobile' ],
],
--- /dev/null
+/**
+ * cldrpluralparser.js
+ * A parser engine for CLDR plural rules.
+ *
+ * Copyright 2012-2014 Santhosh Thottingal and other contributors
+ * Released under the MIT license
+ * http://opensource.org/licenses/MIT
+ *
+ * @version 0.1.0
+ * @source https://github.com/santhoshtr/CLDRPluralRuleParser
+ * @author Santhosh Thottingal <santhosh.thottingal@gmail.com>
+ * @author Timo Tijhof
+ * @author Amir Aharoni
+ */
+
+/**
+ * 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.
+ */
+
+// UMD returnExports https://github.com/umdjs/umd/blob/master/returnExports.js
+(function(root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(factory);
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ // Browser globals (root is window)
+ root.pluralRuleParser = factory();
+ }
+}(this, function() {
+
+function pluralRuleParser(rule, number) {
+ 'use strict';
+
+ /*
+ 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 the 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() {
+ var i, result;
+
+ for (i = 0; i < parserSyntax.length; i++) {
+ 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 i, parserRes,
+ originalPos = pos,
+ result = [];
+
+ for (i = 0; i < parserSyntax.length; i++) {
+ parserRes = parserSyntax[i]();
+
+ if (parserRes === null) {
+ pos = originalPos;
+
+ return null;
+ }
+
+ result.push(parserRes);
+ }
+
+ 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,
+ result = [],
+ 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 i, range_list,
+ result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]);
+
+ if (result !== null) {
+ debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]);
+ range_list = result[4];
+
+ for (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)]),
+ 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, array, left, right,
+ result = sequence([value, _range_, value]);
+
+ if (result !== null) {
+ debug(' -- passed range');
+
+ array = [];
+ left = parseInt(result[0], 10);
+ right = parseInt(result[2], 10);
+
+ for (i = left; i <= right; i++) {
+ array.push(i);
+ }
+
+ return array;
+ }
+
+ debug(' -- failed range');
+
+ return null;
+ }
+
+ function _in() {
+ var result, range_list, i;
+
+ // in_relation = expr ('not')? 'in' range_list
+ result = sequence(
+ [expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]
+ );
+
+ if (result !== null) {
+ debug(' -- passed _in:' + result);
+
+ range_list = result[5];
+
+ for (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() {
+ var range_list, result;
+
+ // within_relation = expr ('not')? 'within' range_list
+ result = sequence(
+ [expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]
+ );
+
+ if (result !== null) {
+ debug(' -- passed within');
+
+ 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 i,
+ result = sequence([relation, nOrMore(0, andTail)]);
+
+ if (result) {
+ if (!result[0]) {
+ return false;
+ }
+
+ for (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 i,
+ result = sequence([and, nOrMore(0, orTail)]);
+
+ if (result) {
+ for (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;
+}
+
+return pluralRuleParser;
+
+}));
--- /dev/null
+/* global JpegMeta */
+( function ( mw ) {
+
+ // Export as module
+ module.exports = function ( fileReaderResult, fileName ) {
+ return new JpegMeta.JpegFile( fileReaderResult, fileName );
+ };
+
+ // Back-compat: Also expose via mw.lib
+ // @deprecated since 1.31
+ mw.log.deprecate( mw.libs, 'jpegmeta', module.exports );
+}( mediaWiki ) );
--- /dev/null
+/**
+ * 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.
+ */
+
+( 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:
+
+ <script type="text/javascript" src="jpegmeta.js"></script>
+
+ 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);
+ }
+ }
+ };
+
+}() );
--- /dev/null
+// Expose via module.exports
+module.exports = window.pluralRuleParser;
+
+// Back-compat: Also expose via mw.lib
+mediaWiki.libs.pluralRuleParser = window.pluralRuleParser;
+++ /dev/null
-/* This is CLDRPluralRuleParser v1.1.3, ported to MediaWiki ResourceLoader */
-
-/**
-* CLDRPluralRuleParser.js
-* A parser engine for CLDR plural rules.
-*
-* Copyright 2012-2014 Santhosh Thottingal and other contributors
-* Released under the MIT license
-* http://opensource.org/licenses/MIT
-*
-* @source https://github.com/santhoshtr/CLDRPluralRuleParser
-* @author Santhosh Thottingal <santhosh.thottingal@gmail.com>
-* @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) {
- 'use strict';
-
- /*
- 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 the 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() {
- var i, result;
-
- for (i = 0; i < parserSyntax.length; i++) {
- 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 i, parserRes,
- originalPos = pos,
- result = [];
-
- for (i = 0; i < parserSyntax.length; i++) {
- parserRes = parserSyntax[i]();
-
- if (parserRes === null) {
- pos = originalPos;
-
- return null;
- }
-
- result.push(parserRes);
- }
-
- 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,
- result = [],
- 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 i, range_list,
- result = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]);
-
- if (result !== null) {
- debug(' -- passed not_in: ' + result[0] + ' != ' + result[4]);
- range_list = result[4];
-
- for (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)]),
- 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, array, left, right,
- result = sequence([value, _range_, value]);
-
- if (result !== null) {
- debug(' -- passed range');
-
- array = [];
- left = parseInt(result[0], 10);
- right = parseInt(result[2], 10);
-
- for (i = left; i <= right; i++) {
- array.push(i);
- }
-
- return array;
- }
-
- debug(' -- failed range');
-
- return null;
- }
-
- function _in() {
- var result, range_list, i;
-
- // in_relation = expr ('not')? 'in' range_list
- result = sequence(
- [expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]
- );
-
- if (result !== null) {
- debug(' -- passed _in:' + result);
-
- range_list = result[5];
-
- for (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() {
- var range_list, result;
-
- // within_relation = expr ('not')? 'within' range_list
- result = sequence(
- [expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]
- );
-
- if (result !== null) {
- debug(' -- passed within');
-
- 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 i,
- result = sequence([relation, nOrMore(0, andTail)]);
-
- if (result) {
- if (!result[0]) {
- return false;
- }
-
- for (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 i,
- result = sequence([and, nOrMore(0, orTail)]);
-
- if (result) {
- for (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;
-module.exports = pluralRuleParser;
-
-} )( mediaWiki );
+++ /dev/null
-/**
- * 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 export as module.
- * - Add mw.libs.jpegmeta wrapper.
- */
-
-( function ( mw ) {
- /*
- 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:
-
- <script type="text/javascript" src="jpegmeta.js"></script>
-
- 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: Export as module
- module.exports = function( fileReaderResult, fileName ) {
- return new JpegMeta.JpegFile( fileReaderResult, fileName );
- };
-
- // MediaWiki: Add mw.libs wrapper
- // @deprecated since 1.31
- mw.log.deprecate( mw.libs, 'jpegmeta', module.exports );
-
-}( mediaWiki ) );
+++ /dev/null
-// Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
-// This affects English only (and languages without localisations, that fall back to English).
-// http://momentjs.com/docs/#/customization/long-date-formats/
-/* global moment */
-moment.updateLocale( 'en', {
- longDateFormat: {
- // Unchanged, but have to be repeated here:
- LT: 'h:mm A',
- LTS: 'h:mm:ss A',
- // Customized:
- L: 'DD/MM/YYYY',
- LL: 'D MMMM YYYY',
- LLL: 'D MMMM YYYY LT',
- LLLL: 'dddd, D MMMM YYYY LT'
- }
-} );
+++ /dev/null
-// Back-compat: Export module as global
-window.moment = module.exports;
+++ /dev/null
-/* global mediaWiki, moment */
-
-( function ( mw ) {
- // HACK: Overwrite moment's i18n with MediaWiki's for the current language so that
- // wgTranslateNumerals is respected.
- moment.updateLocale( moment.locale(), {
- preparse: function ( s ) {
- var i,
- table = mw.language.getDigitTransformTable();
- if ( mw.config.get( 'wgTranslateNumerals' ) ) {
- for ( i = 0; i < 10; i++ ) {
- if ( table[ i ] !== undefined ) {
- s = s.replace( new RegExp( mw.RegExp.escape( table[ i ] ), 'g' ), i );
- }
- }
- }
- // HACK: momentjs replaces commas in some languages, which is the only other use of preparse
- // aside from digit transformation. We can only override preparse, not extend it, so we
- // have to replicate the comma replacement functionality here.
- if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
- s = s.replace( /،/g, ',' );
- }
- return s;
- },
- postformat: function ( s ) {
- var i,
- table = mw.language.getDigitTransformTable();
- if ( mw.config.get( 'wgTranslateNumerals' ) ) {
- for ( i = 0; i < 10; i++ ) {
- if ( table[ i ] !== undefined ) {
- s = s.replace( new RegExp( i, 'g' ), table[ i ] );
- }
- }
- }
- // HACK: momentjs replaces commas in some languages, which is the only other use of postformat
- // aside from digit transformation. We can only override postformat, not extend it, so we
- // have to replicate the comma replacement functionality here.
- if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
- s = s.replace( /,/g, '،' );
- }
- return s;
- }
- } );
-}( mediaWiki ) );
--- /dev/null
+// Use DMY date format for Moment.js, in accordance with MediaWiki's date formatting routines.
+// This affects English only (and languages without localisations, that fall back to English).
+// http://momentjs.com/docs/#/customization/long-date-formats/
+/* global moment */
+moment.updateLocale( 'en', {
+ longDateFormat: {
+ // Unchanged, but have to be repeated here:
+ LT: 'h:mm A',
+ LTS: 'h:mm:ss A',
+ // Customized:
+ L: 'DD/MM/YYYY',
+ LL: 'D MMMM YYYY',
+ LLL: 'D MMMM YYYY LT',
+ LLLL: 'dddd, D MMMM YYYY LT'
+ }
+} );
--- /dev/null
+// Back-compat: Export module as global
+window.moment = module.exports;
--- /dev/null
+/* global mediaWiki, moment */
+
+( function ( mw ) {
+ // HACK: Overwrite moment's i18n with MediaWiki's for the current language so that
+ // wgTranslateNumerals is respected.
+ moment.updateLocale( moment.locale(), {
+ preparse: function ( s ) {
+ var i,
+ table = mw.language.getDigitTransformTable();
+ if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+ for ( i = 0; i < 10; i++ ) {
+ if ( table[ i ] !== undefined ) {
+ s = s.replace( new RegExp( mw.RegExp.escape( table[ i ] ), 'g' ), i );
+ }
+ }
+ }
+ // HACK: momentjs replaces commas in some languages, which is the only other use of preparse
+ // aside from digit transformation. We can only override preparse, not extend it, so we
+ // have to replicate the comma replacement functionality here.
+ if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
+ s = s.replace( /،/g, ',' );
+ }
+ return s;
+ },
+ postformat: function ( s ) {
+ var i,
+ table = mw.language.getDigitTransformTable();
+ if ( mw.config.get( 'wgTranslateNumerals' ) ) {
+ for ( i = 0; i < 10; i++ ) {
+ if ( table[ i ] !== undefined ) {
+ s = s.replace( new RegExp( i, 'g' ), table[ i ] );
+ }
+ }
+ }
+ // HACK: momentjs replaces commas in some languages, which is the only other use of postformat
+ // aside from digit transformation. We can only override postformat, not extend it, so we
+ // have to replicate the comma replacement functionality here.
+ if ( [ 'ar', 'ar-sa', 'fa' ].indexOf( mw.config.get( 'wgUserLanguage' ) ) !== -1 ) {
+ s = s.replace( /,/g, '،' );
+ }
+ return s;
+ }
+ } );
+}( mediaWiki ) );
->getMock();
$skin->expects( $this->once() )->method( 'getDefaultModules' )
->willReturn( [
+ 'styles' => [ 'core' => [ 'quux.styles' ] ],
'core' => [ 'foo', 'bar' ],
'content' => [ 'baz' ]
] );
'resp.parse.modulescripts'
);
$this->assertSame(
- [ 'foo.styles' ],
+ [ 'foo.styles', 'quux.styles' ],
$res[0]['parse']['modulestyles'],
'resp.parse.modulestyles'
);
/**
* @dataProvider provideGetStyles
- * @covers ResourceLoaderSkinModule::normalizeStyles
- * @covers ResourceLoaderSkinModule::getStyles
+ * @covers ResourceLoaderSkinModule
*/
public function testGetStyles( $parent, $logo, $expected ) {
$module = $this->getMockBuilder( ResourceLoaderSkinModule::class )