13 changed files with 1154 additions and 33 deletions
@ -0,0 +1,437 @@ |
|||||||
|
/* 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) + '%'); |
||||||
|
}); |
||||||
|
}; |
@ -0,0 +1,211 @@ |
|||||||
|
/* 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. |
||||||
|
*/ |
||||||
|
|
||||||
|
exports.optimizeCMap = function (data) { |
||||||
|
var i = 1; |
||||||
|
while (i < data.body.length) { |
||||||
|
if (data.body[i - 1].type === data.body[i].type) { |
||||||
|
data.body[i - 1].items = data.body[i - 1].items.concat(data.body[i].items); |
||||||
|
data.body.splice(i, 1); |
||||||
|
} else { |
||||||
|
i++; |
||||||
|
} |
||||||
|
} |
||||||
|
// split into groups with different lengths
|
||||||
|
var i = 0; |
||||||
|
while (i < data.body.length) { |
||||||
|
var item = data.body[i]; |
||||||
|
var keys = Object.keys(item.items[0]).filter(function (i) { |
||||||
|
return typeof item.items[0][i] === 'string'; |
||||||
|
}); |
||||||
|
var j = 1; |
||||||
|
while (j < item.items.length) { |
||||||
|
var different = false; |
||||||
|
for (var q = 0; q < keys.length && !different; q++) { |
||||||
|
different = item.items[j - 1][keys[q]].length !== item.items[j][keys[q]].length; |
||||||
|
} |
||||||
|
if (different) { |
||||||
|
break; |
||||||
|
} |
||||||
|
j++; |
||||||
|
} |
||||||
|
if (j < item.items.length) { |
||||||
|
data.body.splice(i + 1, 0, { |
||||||
|
type: item.type, |
||||||
|
items: item.items.splice(j, item.items.length - j) |
||||||
|
}); |
||||||
|
} |
||||||
|
i++; |
||||||
|
} |
||||||
|
// find sequences of single char ranges
|
||||||
|
var i = 0; |
||||||
|
while (i < data.body.length) { |
||||||
|
var item = data.body[i]; |
||||||
|
if (item.type === 3 || item.type === 5) { |
||||||
|
var j = 0; |
||||||
|
while (j < item.items.length) { |
||||||
|
var q = j; |
||||||
|
while (j < item.items.length && item.items[j].start === item.items[j].end) { |
||||||
|
j++; |
||||||
|
} |
||||||
|
if ((j - q) >= 9) { |
||||||
|
if (j < item.items.length) { |
||||||
|
data.body.splice(i + 1, 0, { |
||||||
|
type: item.type, |
||||||
|
items: item.items.splice(j, item.items.length - j) |
||||||
|
}); |
||||||
|
} |
||||||
|
if (q > 0) { |
||||||
|
data.body.splice(i + 1, 0, { |
||||||
|
type: item.type - 1, |
||||||
|
items: item.items.splice(q, j - q).map(function (i) { |
||||||
|
return {char: i.start, code: i.code }; |
||||||
|
}) |
||||||
|
}); |
||||||
|
i++; |
||||||
|
} else { |
||||||
|
item.type -= 1; |
||||||
|
item.items = item.items.map(function (i) { |
||||||
|
return {char: i.start, code: i.code }; |
||||||
|
}); |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
j++; |
||||||
|
} |
||||||
|
} |
||||||
|
i++; |
||||||
|
} |
||||||
|
|
||||||
|
// find sequences of increasing code/ranges order
|
||||||
|
var i = 0; |
||||||
|
while (i < data.body.length) { |
||||||
|
var item = data.body[i]; |
||||||
|
if (item.type >= 2 && item.type <= 5) { |
||||||
|
var j = 1; |
||||||
|
var startProp = item.type === 2 || item.type === 4 ? 'char' : 'start'; |
||||||
|
var endProp = item.type === 2 || item.type === 4 ? 'char' : 'end'; |
||||||
|
while (j < item.items.length) { |
||||||
|
var q = j - 1; |
||||||
|
while (j < item.items.length && incHex(item.items[j - 1][endProp]) === item.items[j][startProp]) { |
||||||
|
j++; |
||||||
|
} |
||||||
|
if ((j - q) >= 9) { |
||||||
|
if (j < item.items.length) { |
||||||
|
data.body.splice(i + 1, 0, { |
||||||
|
type: item.type, |
||||||
|
items: item.items.splice(j, item.items.length - j) |
||||||
|
}); |
||||||
|
} |
||||||
|
if (q > 0) { |
||||||
|
data.body.splice(i + 1, 0, { |
||||||
|
type: item.type, |
||||||
|
items: item.items.splice(q, j - q), |
||||||
|
sequence: true |
||||||
|
}); |
||||||
|
i++; |
||||||
|
} else { |
||||||
|
item.sequence = true; |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
j++; |
||||||
|
} |
||||||
|
} |
||||||
|
i++; |
||||||
|
} |
||||||
|
|
||||||
|
// split non-sequences two groups where codes are close
|
||||||
|
var i = 0; |
||||||
|
while (i < data.body.length) { |
||||||
|
var item = data.body[i]; |
||||||
|
if (!item.sequence && (item.type === 2 || item.type === 3)) { |
||||||
|
var subitems = item.items; |
||||||
|
var codes = subitems.map(function (i) { |
||||||
|
return i.code; |
||||||
|
}); |
||||||
|
codes.sort(function (a, b) { |
||||||
|
return a - b; |
||||||
|
}); |
||||||
|
var maxDistance = 100, minItems = 10, itemsPerBucket = 50; |
||||||
|
if (subitems.length > minItems && codes[codes.length - 1] - codes[0] > maxDistance) { |
||||||
|
var gapsCount = Math.max(2, (subitems.length / itemsPerBucket) | 0); |
||||||
|
var gaps = []; |
||||||
|
for (var q = 0; q < gapsCount; q++) { |
||||||
|
gaps.push({length: 0}); |
||||||
|
} |
||||||
|
for (var j = 1; j < codes.length; j++) { |
||||||
|
var gapLength = codes[j] - codes[j - 1]; |
||||||
|
var q = 0; |
||||||
|
while (q < gaps.length && gaps[q].length > gapLength) { |
||||||
|
q++; |
||||||
|
} |
||||||
|
if (q >= gaps.length) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
var q0 = q; |
||||||
|
while (q < gaps.length) { |
||||||
|
if (gaps[q].length < gaps[q0].length) { |
||||||
|
q0 = q; |
||||||
|
} |
||||||
|
q++; |
||||||
|
} |
||||||
|
gaps[q0] = {length: gapLength, boundary: codes[j]}; |
||||||
|
} |
||||||
|
var groups = gaps.filter(function (g) { |
||||||
|
return g.length >= maxDistance; |
||||||
|
}).map(function (g) { |
||||||
|
return g.boundary; |
||||||
|
}); |
||||||
|
groups.sort(function (a, b) { |
||||||
|
return a - b; |
||||||
|
}); |
||||||
|
if (groups.length > 1) { |
||||||
|
var buckets = [item.items = []]; |
||||||
|
for (var j = 0; j < groups.length; j++) { |
||||||
|
var newItem = {type: item.type, items: []} |
||||||
|
buckets.push(newItem.items); |
||||||
|
i++; |
||||||
|
data.body.splice(i, 0, newItem); |
||||||
|
} |
||||||
|
for (var j = 0; j < subitems.length; j++) { |
||||||
|
var code = subitems[j].code; |
||||||
|
var q = 0; |
||||||
|
while (q < groups.length && groups[q] <= code) { |
||||||
|
q++; |
||||||
|
} |
||||||
|
buckets[q].push(subitems[j]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
i++; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
function incHex(a) { |
||||||
|
var c = 1, s = ''; |
||||||
|
for (var i = a.length - 1; i >= 0; i--) { |
||||||
|
c += parseInt(a[i], 16); |
||||||
|
if (c >= 16) { |
||||||
|
s = '0' + s; |
||||||
|
c = 1; |
||||||
|
} else { |
||||||
|
s = c.toString(16) + s; |
||||||
|
c = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
/* 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. |
||||||
|
*/ |
||||||
|
|
||||||
|
exports.parseAdobeCMap = function (content) { |
||||||
|
var m = /(\bbegincmap\b[\s\S]*?)\bendcmap\b/.exec(content); |
||||||
|
if (!m) { |
||||||
|
throw new Error('cmap was not found'); |
||||||
|
} |
||||||
|
|
||||||
|
var body = m[1].replace(/\r\n?/g, '\n'); |
||||||
|
var result = { |
||||||
|
type: 1, |
||||||
|
wmode: 0, |
||||||
|
comment: 'Copyright 1990-2009 Adobe Systems Incorporated.\nAll rights reserved.\nhttp://sourceforge.net/adobe/cmap/wiki/License/', |
||||||
|
usecmap: null, |
||||||
|
body: [] |
||||||
|
}; |
||||||
|
m = /\/CMapType\s+(\d+)+\s+def\b/.exec(body); |
||||||
|
result.type = +m[1]; |
||||||
|
m = /\/WMode\s+(\d+)+\s+def\b/.exec(body); |
||||||
|
result.wmode = +m[1]; |
||||||
|
m = /\/([\w\-]+)\s+usecmap\b/.exec(body); |
||||||
|
if (m) { |
||||||
|
result.usecmap = m[1]; |
||||||
|
} |
||||||
|
var re = /(\d+)\s+(begincodespacerange|beginnotdefrange|begincidchar|begincidrange|beginbfchar|beginbfrange)\n([\s\S]*?)\n(endcodespacerange|endnotdefrange|endcidchar|endcidrange|endbfchar|endbfrange)/g; |
||||||
|
while (m = re.exec(body)) { |
||||||
|
var lines = m[3].toLowerCase().split('\n'); |
||||||
|
var m2; |
||||||
|
switch (m[2]) { |
||||||
|
case 'begincodespacerange': |
||||||
|
result.body.push({ |
||||||
|
type: 0, |
||||||
|
items: lines.map(function (line) { |
||||||
|
var m = /<(\w+)>\s+<(\w+)>/.exec(line); |
||||||
|
return {start: m[1], end: m[2]}; |
||||||
|
}) |
||||||
|
}); |
||||||
|
break; |
||||||
|
case 'beginnotdefrange': |
||||||
|
result.body.push({ |
||||||
|
type: 1, |
||||||
|
items: lines.map(function (line) { |
||||||
|
var m = /<(\w+)>\s+<(\w+)>\s+(\d+)/.exec(line); |
||||||
|
return {start: m[1], end: m[2], code: +m[3]}; |
||||||
|
}) |
||||||
|
}); |
||||||
|
break; |
||||||
|
case 'begincidchar': |
||||||
|
result.body.push({ |
||||||
|
type: 2, |
||||||
|
items: lines.map(function (line) { |
||||||
|
var m = /<(\w+)>\s+(\d+)/.exec(line); |
||||||
|
return {char: m[1], code: +m[2]}; |
||||||
|
}) |
||||||
|
}); |
||||||
|
break; |
||||||
|
case 'begincidrange': |
||||||
|
result.body.push({ |
||||||
|
type: 3, |
||||||
|
items: lines.map(function (line) { |
||||||
|
var m = /<(\w+)>\s+<(\w+)>\s+(\d+)/.exec(line); |
||||||
|
return {start: m[1], end: m[2], code: +m[3]}; |
||||||
|
}) |
||||||
|
}); |
||||||
|
break; |
||||||
|
case 'beginbfchar': |
||||||
|
result.body.push({ |
||||||
|
type: 4, |
||||||
|
items: lines.map(function (line) { |
||||||
|
var m = /<(\w+)>\s+<(\w+)>/.exec(line); |
||||||
|
return {char: m[1], code: m[2]}; |
||||||
|
}) |
||||||
|
}); |
||||||
|
break; |
||||||
|
case 'beginbfrange': |
||||||
|
result.body.push({ |
||||||
|
type: 5, |
||||||
|
items: lines.map(function (line) { |
||||||
|
var m = /<(\w+)>\s+<(\w+)>\s+<(\w+)>/.exec(line); |
||||||
|
return {start: m[1], end: m[2], code: m[3]}; |
||||||
|
}) |
||||||
|
}); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
}; |
@ -1,3 +1,4 @@ |
|||||||
viewer-production.html |
viewer-production.html |
||||||
locale.properties |
locale.properties |
||||||
locale/ |
locale/ |
||||||
|
cmaps/ |
||||||
|
Loading…
Reference in new issue