You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
14 KiB
437 lines
14 KiB
/* Copyright 2014 Mozilla Foundation |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
var fs = require('fs'); |
|
var path = require('path'); |
|
var parseAdobeCMap = require('./parse.js').parseAdobeCMap; |
|
var optimizeCMap = require('./optimize.js').optimizeCMap; |
|
|
|
function compressCmap(srcPath, destPath, verify) { |
|
var content = fs.readFileSync(srcPath).toString(); |
|
var inputData = parseAdobeCMap(content); |
|
optimizeCMap(inputData); |
|
|
|
var out = writeByte((inputData.type << 1) | inputData.wmode); |
|
if (inputData.comment) { |
|
out += writeByte(0xE0) + writeString(inputData.comment); |
|
} |
|
if (inputData.usecmap) { |
|
out += writeByte(0xE1) + writeString(inputData.usecmap); |
|
} |
|
var i = 0; |
|
while (i < inputData.body.length) { |
|
var item = inputData.body[i++], subitems = item.items; |
|
var first = item.items[0]; |
|
var sequence = item.sequence === true; |
|
var flags = (item.type << 5) | (sequence ? 0x10 : 0); |
|
var nextStart, nextCode; |
|
switch (item.type) { |
|
case 0: |
|
out += writeByte(flags | getHexSize(first.start)) + writeNumber(subitems.length); |
|
out += first.start + writeNumber(subHex(first.end, first.start)); |
|
nextStart = incHex(first.end); |
|
for (var j = 1; j < subitems.length; j++) { |
|
out += writeNumber(subHex(subitems[j].start, nextStart)) + |
|
writeNumber(subHex(subitems[j].end, subitems[j].start)); |
|
nextStart = incHex(subitems[j].end); |
|
} |
|
break; |
|
case 1: |
|
out += writeByte(flags | getHexSize(first.start)) + writeNumber(subitems.length); |
|
out += first.start + writeNumber(subHex(first.end, first.start)) + writeNumber(first.code); |
|
nextStart = incHex(first.end); |
|
for (var j = 1; j < subitems.length; j++) { |
|
out += writeNumber(subHex(subitems[j].start, nextStart)) + |
|
writeNumber(subHex(subitems[j].end, subitems[j].start)) + |
|
writeNumber(subitems[j].code); |
|
nextStart = incHex(subitems[j].end); |
|
} |
|
break; |
|
case 2: |
|
out += writeByte(flags | getHexSize(first.char)) + writeNumber(subitems.length); |
|
out += first.char + writeNumber(first.code); |
|
nextStart = incHex(first.char); |
|
nextCode = first.code + 1; |
|
for (var j = 1; j < subitems.length; j++) { |
|
out += (sequence ? '' : writeNumber(subHex(subitems[j].char, nextStart))) + |
|
writeSigned(subitems[j].code - nextCode); |
|
nextStart = incHex(subitems[j].char); |
|
nextCode = item.items[j].code + 1; |
|
} |
|
break; |
|
case 3: |
|
out += writeByte(flags | getHexSize(first.start)) + writeNumber(subitems.length); |
|
out += first.start + writeNumber(subHex(first.end, first.start)) + writeNumber(first.code); |
|
nextStart = incHex(first.end); |
|
for (var j = 1; j < subitems.length; j++) { |
|
out += (sequence ? '' : writeNumber(subHex(subitems[j].start, nextStart))) + |
|
writeNumber(subHex(subitems[j].end, subitems[j].start)) + |
|
writeNumber(subitems[j].code); |
|
nextStart = incHex(subitems[j].end); |
|
} |
|
break; |
|
case 4: |
|
out += writeByte(flags | getHexSize(first.code)) + writeNumber(subitems.length); |
|
out += first.char + first.code; |
|
nextStart = incHex(first.char); |
|
nextCode = incHex(first.code); |
|
for (var j = 1; j < subitems.length; j++) { |
|
out += (sequence ? '' : writeNumber(subHex(subitems[j].char, nextStart))) + |
|
writeSigned(subHex(subitems[j].code, nextCode)); |
|
nextStart = incHex(subitems[j].char); |
|
nextCode = incHex(subitems[j].code); |
|
} |
|
break; |
|
case 5: |
|
out += writeByte(flags | getHexSize(first.code)) + writeNumber(subitems.length); |
|
out += first.start + writeNumber(subHex(first.end, first.start)) + first.code; |
|
nextStart = incHex(first.end); |
|
for (var j = 1; j < subitems.length; j++) { |
|
out += (sequence ? '' : writeNumber(subHex(subitems[j].start, nextStart))) + |
|
writeNumber(subHex(subitems[j].end, subitems[j].start)) + |
|
subitems[j].code; |
|
nextStart = incHex(subitems[j].end); |
|
} |
|
break; |
|
|
|
} |
|
} |
|
|
|
fs.writeFileSync(destPath, new Buffer(out, 'hex')); |
|
|
|
if (verify) { |
|
var result2 = parseCMap(out); |
|
var isGood = JSON.stringify(inputData) == JSON.stringify(result2); |
|
if (!isGood) { |
|
throw new Error('Extracted data does not match the expected result'); |
|
} |
|
} |
|
|
|
return { |
|
orig: fs.statSync(srcPath).size, |
|
packed: out.length >> 1 |
|
}; |
|
} |
|
|
|
function parseCMap(binaryData) { |
|
var reader = { |
|
buffer: binaryData, |
|
pos: 0, |
|
end: binaryData.length, |
|
readByte: function () { |
|
if (this.pos >= this.end) { |
|
return -1; |
|
} |
|
var d1 = fromHexDigit(this.buffer[this.pos]); |
|
var d2 = fromHexDigit(this.buffer[this.pos + 1]); |
|
this.pos += 2; |
|
return (d1 << 4) | d2; |
|
}, |
|
readNumber: function () { |
|
var n = 0; |
|
var last; |
|
do { |
|
var b = this.readByte(); |
|
last = !(b & 0x80); |
|
n = (n << 7) | (b & 0x7F); |
|
} while (!last); |
|
return n; |
|
}, |
|
readSigned: function () { |
|
var n = this.readNumber(); |
|
return (n & 1) ? -(n >>> 1) - 1 : n >>> 1; |
|
}, |
|
readHex: function (size) { |
|
var lengthInChars = (size + 1) << 1; |
|
var s = this.buffer.substr(this.pos, lengthInChars); |
|
this.pos += lengthInChars; |
|
return s; |
|
}, |
|
readHexNumber: function (size) { |
|
var lengthInChars = (size + 1) << 1; |
|
var stack = []; |
|
do { |
|
var b = this.readByte(); |
|
last = !(b & 0x80); |
|
stack.push(b & 0x7F); |
|
} while (!last); |
|
var s = '', buffer = 0, bufferSize = 0; |
|
while (s.length < lengthInChars) { |
|
while (bufferSize < 4 && stack.length > 0) { |
|
buffer = (stack.pop() << bufferSize) | buffer; |
|
bufferSize += 7; |
|
} |
|
s = toHexDigit(buffer & 15) + s; |
|
buffer >>= 4; |
|
bufferSize -= 4; |
|
} |
|
return s; |
|
}, |
|
readHexSigned: function (size) { |
|
var num = this.readHexNumber(size); |
|
var sign = fromHexDigit(num[num.length - 1]) & 1 ? 15 : 0; |
|
var c = 0; |
|
var result = ''; |
|
for (var i = 0; i < num.length; i++) { |
|
c = (c << 4) | fromHexDigit(num[i]); |
|
result += toHexDigit(sign ? (c >> 1) ^ sign : (c >> 1)); |
|
c &= 1; |
|
} |
|
return result; |
|
}, |
|
readString: function () { |
|
var len = this.readNumber(); |
|
var s = ''; |
|
for (var i = 0; i < len; i++) { |
|
s += String.fromCharCode(this.readNumber()); |
|
} |
|
return s; |
|
} |
|
}; |
|
|
|
var header = reader.readByte(); |
|
var result = { |
|
type: header >> 1, |
|
wmode: header & 1, |
|
comment: null, |
|
usecmap: null, |
|
body: [] |
|
}; |
|
|
|
var b; |
|
while ((b = reader.readByte()) >= 0) { |
|
var type = b >> 5; |
|
if (type === 7) { |
|
switch (b & 0x1F) { |
|
case 0: |
|
result.comment = reader.readString(); |
|
break; |
|
case 1: |
|
result.usecmap = reader.readString(); |
|
break; |
|
} |
|
continue; |
|
} |
|
var sequence = !!(b & 0x10); |
|
var dataSize = b & 15; |
|
var subitems = []; |
|
var item = { |
|
type: type, |
|
items: subitems |
|
}; |
|
if (sequence) { |
|
item.sequence = true; |
|
} |
|
var ucs2DataSize = 1; |
|
var subitemsCount = reader.readNumber(); |
|
var start, end, code, char; |
|
switch (type) { |
|
case 0: |
|
start = reader.readHex(dataSize); |
|
end = addHex(reader.readHexNumber(dataSize), start); |
|
subitems.push({start: start, end: end}); |
|
for (var i = 1; i < subitemsCount; i++) { |
|
start = addHex(reader.readHexNumber(dataSize), incHex(end)); |
|
end = addHex(reader.readHexNumber(dataSize), start); |
|
subitems.push({start: start, end: end}); |
|
} |
|
break; |
|
case 1: |
|
start = reader.readHex(dataSize); |
|
end = addHex(reader.readHexNumber(dataSize), start); |
|
code = reader.readNumber(); |
|
subitems.push({start: start, end: end, code: code}); |
|
for (var i = 1; i < subitemsCount; i++) { |
|
start = addHex(reader.readHexNumber(dataSize), incHex(end)); |
|
end = addHex(reader.readHexNumber(dataSize), start); |
|
code = reader.readNumber(); |
|
subitems.push({start: start, end: end, code: code}); |
|
} |
|
break; |
|
case 2: |
|
char = reader.readHex(dataSize); |
|
code = reader.readNumber(); |
|
subitems.push({char: char, code: code}); |
|
for (var i = 1; i < subitemsCount; i++) { |
|
char = sequence ? incHex(char) : addHex(reader.readHexNumber(dataSize), incHex(char)); |
|
code = reader.readSigned() + (code + 1); |
|
subitems.push({char: char, code: code}); |
|
} |
|
break; |
|
case 3: |
|
start = reader.readHex(dataSize); |
|
end = addHex(reader.readHexNumber(dataSize), start); |
|
code = reader.readNumber(); |
|
subitems.push({start: start, end: end, code: code}); |
|
for (var i = 1; i < subitemsCount; i++) { |
|
start = sequence ? incHex(end) : addHex(reader.readHexNumber(dataSize), incHex(end)); |
|
end = addHex(reader.readHexNumber(dataSize), start); |
|
code = reader.readNumber(); |
|
subitems.push({start: start, end: end, code: code}); |
|
} |
|
break; |
|
case 4: |
|
char = reader.readHex(ucs2DataSize); |
|
code = reader.readHex(dataSize); |
|
subitems.push({char: char, code: code}); |
|
for (var i = 1; i < subitemsCount; i++) { |
|
char = sequence ? incHex(char) : addHex(reader.readHexNumber(ucs2DataSize), incHex(char)); |
|
code = addHex(reader.readHexSigned(dataSize), incHex(code)); |
|
subitems.push({char: char, code: code}); |
|
} |
|
break; |
|
case 5: |
|
start = reader.readHex(ucs2DataSize); |
|
end = addHex(reader.readHexNumber(ucs2DataSize), start); |
|
code = reader.readHex(dataSize); |
|
subitems.push({start: start, end: end, code: code}); |
|
for (var i = 1; i < subitemsCount; i++) { |
|
start = sequence ? incHex(end) : addHex(reader.readHexNumber(ucs2DataSize), incHex(end)); |
|
end = addHex(reader.readHexNumber(ucs2DataSize), start); |
|
code = reader.readHex(dataSize); |
|
subitems.push({start: start, end: end, code: code}); |
|
} |
|
break; |
|
default: |
|
throw new Error('Unknown type: ' + type) |
|
} |
|
result.body.push(item); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
function toHexDigit(n) { |
|
return n.toString(16); |
|
} |
|
function fromHexDigit(s) { |
|
return parseInt(s, 16); |
|
} |
|
function getHexSize(s) { |
|
return (s.length >> 1) - 1; |
|
} |
|
function writeByte(b) { |
|
return toHexDigit((b >> 4) & 15) + toHexDigit(b & 15); |
|
} |
|
function writeNumber(n) { |
|
if (typeof n === 'string') { |
|
var s = '', buffer = 0, bufferSize = 0; |
|
var i = n.length; |
|
while (i > 0) { |
|
--i; |
|
buffer = (fromHexDigit(n[i]) << bufferSize) | buffer; |
|
bufferSize += 4; |
|
if (bufferSize >= 7) { |
|
s = writeByte((buffer & 0x7f) | (s.length > 0 ? 0x80 : 0)) + s; |
|
buffer >>>= 7; |
|
bufferSize -= 7; |
|
} |
|
} |
|
if (buffer > 0) { |
|
s = writeByte((buffer & 0x7f) | (s.length > 0 ? 0x80 : 0)) + s; |
|
} |
|
while (s.indexOf('80') === 0) { |
|
s = s.substr(2); |
|
} |
|
return s; |
|
} else { |
|
var s = writeByte(n & 0x7F); |
|
n >>>= 7; |
|
while (n > 0) { |
|
s = writeByte((n & 0x7F) | 0x80) + s; |
|
n >>>= 7; |
|
} |
|
return s; |
|
} |
|
} |
|
function writeSigned(n) { |
|
if (typeof n === 'string') { |
|
var t = ''; |
|
var c = fromHexDigit(n[0]); |
|
var neg = c >= 8; |
|
c = neg ? (c ^ 15) : c; |
|
for (var i = 1; i < n.length; i++) { |
|
var d = fromHexDigit(n[i]); |
|
c = (c << 4) | (neg ? (d ^ 15) : d); |
|
t += toHexDigit(c >> 3); |
|
c = c & 7; |
|
} |
|
t += toHexDigit((c << 1) | (neg ? 1 : 0)); |
|
return writeNumber(t); |
|
} |
|
return n < 0 ? writeNumber(-2 * n - 1) : writeNumber(2 * n); |
|
} |
|
function writeString(s) { |
|
var t = writeNumber(s.length); |
|
for (var i = 0; i < s.length; i++) { |
|
t += writeNumber(s.charCodeAt(i)); |
|
} |
|
return t; |
|
} |
|
function addHex(a, b) { |
|
var c = 0, s = ''; |
|
for (var i = a.length - 1; i >= 0; i--) { |
|
c += fromHexDigit(a[i]) + fromHexDigit(b[i]); |
|
if (c >= 16) { |
|
s = toHexDigit(c - 16) + s; |
|
c = 1; |
|
} else { |
|
s = toHexDigit(c) + s; |
|
c = 0; |
|
} |
|
} |
|
return s; |
|
} |
|
function subHex(a, b) { |
|
var c = 0, s = ''; |
|
for (var i = a.length - 1; i >= 0; i--) { |
|
c += fromHexDigit(a[i]) - fromHexDigit(b[i]); |
|
if (c < 0) { |
|
s = toHexDigit(c + 16) + s; |
|
c = -1; |
|
} else { |
|
s = toHexDigit(c) + s; |
|
c = 0; |
|
} |
|
} |
|
return s; |
|
} |
|
function incHex(a) { |
|
var c = 1, s = ''; |
|
for (var i = a.length - 1; i >= 0; i--) { |
|
c += fromHexDigit(a[i]); |
|
if (c >= 16) { |
|
s = toHexDigit(c - 16) + s; |
|
c = 1; |
|
} else { |
|
s = toHexDigit(c) + s; |
|
c = 0; |
|
} |
|
} |
|
return s; |
|
} |
|
|
|
exports.compressCmaps = function (src, dest, verify) { |
|
var files = fs.readdirSync(src).filter(function (fn) { |
|
return fn.indexOf('.') < 0; // skipping files with the extension |
|
}); |
|
files.forEach(function (fn) { |
|
var srcPath = path.join(src, fn); |
|
var destPath = path.join(dest, fn + '.bcmap'); |
|
var stats = compressCmap(srcPath, destPath, verify); |
|
console.log('Compressing ' + fn + ': ' + stats.orig + ' vs ' + stats.packed + |
|
' ' + (stats.packed / stats.orig * 100).toFixed(1) + '%'); |
|
}); |
|
};
|
|
|