Generic build of PDF.js library.
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.
 
 

745 lines
24 KiB

/* Copyright 2017 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.
*/
'use strict';
var sharedUtil = require('../shared/util.js');
var coreStream = require('./stream.js');
var coreGlyphList = require('./glyphlist.js');
var coreEncodings = require('./encodings.js');
var coreCFFParser = require('./cff_parser.js');
var Util = sharedUtil.Util;
var bytesToString = sharedUtil.bytesToString;
var error = sharedUtil.error;
var Stream = coreStream.Stream;
var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
var StandardEncoding = coreEncodings.StandardEncoding;
var CFFParser = coreCFFParser.CFFParser;
var FontRendererFactory = function FontRendererFactoryClosure() {
function getLong(data, offset) {
return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
}
function getUshort(data, offset) {
return data[offset] << 8 | data[offset + 1];
}
function parseCmap(data, start, end) {
var offset = getUshort(data, start + 2) === 1 ? getLong(data, start + 8) : getLong(data, start + 16);
var format = getUshort(data, start + offset);
var ranges, p, i;
if (format === 4) {
getUshort(data, start + offset + 2);
var segCount = getUshort(data, start + offset + 6) >> 1;
p = start + offset + 14;
ranges = [];
for (i = 0; i < segCount; i++, p += 2) {
ranges[i] = { end: getUshort(data, p) };
}
p += 2;
for (i = 0; i < segCount; i++, p += 2) {
ranges[i].start = getUshort(data, p);
}
for (i = 0; i < segCount; i++, p += 2) {
ranges[i].idDelta = getUshort(data, p);
}
for (i = 0; i < segCount; i++, p += 2) {
var idOffset = getUshort(data, p);
if (idOffset === 0) {
continue;
}
ranges[i].ids = [];
for (var j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) {
ranges[i].ids[j] = getUshort(data, p + idOffset);
idOffset += 2;
}
}
return ranges;
} else if (format === 12) {
getLong(data, start + offset + 4);
var groups = getLong(data, start + offset + 12);
p = start + offset + 16;
ranges = [];
for (i = 0; i < groups; i++) {
ranges.push({
start: getLong(data, p),
end: getLong(data, p + 4),
idDelta: getLong(data, p + 8) - getLong(data, p)
});
p += 12;
}
return ranges;
}
error('not supported cmap: ' + format);
}
function parseCff(data, start, end, seacAnalysisEnabled) {
var properties = {};
var parser = new CFFParser(new Stream(data, start, end - start), properties, seacAnalysisEnabled);
var cff = parser.parse();
return {
glyphs: cff.charStrings.objects,
subrs: cff.topDict.privateDict && cff.topDict.privateDict.subrsIndex && cff.topDict.privateDict.subrsIndex.objects,
gsubrs: cff.globalSubrIndex && cff.globalSubrIndex.objects
};
}
function parseGlyfTable(glyf, loca, isGlyphLocationsLong) {
var itemSize, itemDecode;
if (isGlyphLocationsLong) {
itemSize = 4;
itemDecode = function fontItemDecodeLong(data, offset) {
return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
};
} else {
itemSize = 2;
itemDecode = function fontItemDecode(data, offset) {
return data[offset] << 9 | data[offset + 1] << 1;
};
}
var glyphs = [];
var startOffset = itemDecode(loca, 0);
for (var j = itemSize; j < loca.length; j += itemSize) {
var endOffset = itemDecode(loca, j);
glyphs.push(glyf.subarray(startOffset, endOffset));
startOffset = endOffset;
}
return glyphs;
}
function lookupCmap(ranges, unicode) {
var code = unicode.charCodeAt(0),
gid = 0;
var l = 0,
r = ranges.length - 1;
while (l < r) {
var c = l + r + 1 >> 1;
if (code < ranges[c].start) {
r = c - 1;
} else {
l = c;
}
}
if (ranges[l].start <= code && code <= ranges[l].end) {
gid = ranges[l].idDelta + (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code) & 0xFFFF;
}
return {
charCode: code,
glyphId: gid
};
}
function compileGlyf(code, cmds, font) {
function moveTo(x, y) {
cmds.push({
cmd: 'moveTo',
args: [x, y]
});
}
function lineTo(x, y) {
cmds.push({
cmd: 'lineTo',
args: [x, y]
});
}
function quadraticCurveTo(xa, ya, x, y) {
cmds.push({
cmd: 'quadraticCurveTo',
args: [xa, ya, x, y]
});
}
var i = 0;
var numberOfContours = (code[i] << 24 | code[i + 1] << 16) >> 16;
var flags;
var x = 0,
y = 0;
i += 10;
if (numberOfContours < 0) {
do {
flags = code[i] << 8 | code[i + 1];
var glyphIndex = code[i + 2] << 8 | code[i + 3];
i += 4;
var arg1, arg2;
if (flags & 0x01) {
arg1 = (code[i] << 24 | code[i + 1] << 16) >> 16;
arg2 = (code[i + 2] << 24 | code[i + 3] << 16) >> 16;
i += 4;
} else {
arg1 = code[i++];
arg2 = code[i++];
}
if (flags & 0x02) {
x = arg1;
y = arg2;
} else {
x = 0;
y = 0;
}
var scaleX = 1,
scaleY = 1,
scale01 = 0,
scale10 = 0;
if (flags & 0x08) {
scaleX = scaleY = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
i += 2;
} else if (flags & 0x40) {
scaleX = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
scaleY = (code[i + 2] << 24 | code[i + 3] << 16) / 1073741824;
i += 4;
} else if (flags & 0x80) {
scaleX = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
scale01 = (code[i + 2] << 24 | code[i + 3] << 16) / 1073741824;
scale10 = (code[i + 4] << 24 | code[i + 5] << 16) / 1073741824;
scaleY = (code[i + 6] << 24 | code[i + 7] << 16) / 1073741824;
i += 8;
}
var subglyph = font.glyphs[glyphIndex];
if (subglyph) {
cmds.push({ cmd: 'save' });
cmds.push({
cmd: 'transform',
args: [scaleX, scale01, scale10, scaleY, x, y]
});
compileGlyf(subglyph, cmds, font);
cmds.push({ cmd: 'restore' });
}
} while (flags & 0x20);
} else {
var endPtsOfContours = [];
var j, jj;
for (j = 0; j < numberOfContours; j++) {
endPtsOfContours.push(code[i] << 8 | code[i + 1]);
i += 2;
}
var instructionLength = code[i] << 8 | code[i + 1];
i += 2 + instructionLength;
var numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1;
var points = [];
while (points.length < numberOfPoints) {
flags = code[i++];
var repeat = 1;
if (flags & 0x08) {
repeat += code[i++];
}
while (repeat-- > 0) {
points.push({ flags: flags });
}
}
for (j = 0; j < numberOfPoints; j++) {
switch (points[j].flags & 0x12) {
case 0x00:
x += (code[i] << 24 | code[i + 1] << 16) >> 16;
i += 2;
break;
case 0x02:
x -= code[i++];
break;
case 0x12:
x += code[i++];
break;
}
points[j].x = x;
}
for (j = 0; j < numberOfPoints; j++) {
switch (points[j].flags & 0x24) {
case 0x00:
y += (code[i] << 24 | code[i + 1] << 16) >> 16;
i += 2;
break;
case 0x04:
y -= code[i++];
break;
case 0x24:
y += code[i++];
break;
}
points[j].y = y;
}
var startPoint = 0;
for (i = 0; i < numberOfContours; i++) {
var endPoint = endPtsOfContours[i];
var contour = points.slice(startPoint, endPoint + 1);
if (contour[0].flags & 1) {
contour.push(contour[0]);
} else if (contour[contour.length - 1].flags & 1) {
contour.unshift(contour[contour.length - 1]);
} else {
var p = {
flags: 1,
x: (contour[0].x + contour[contour.length - 1].x) / 2,
y: (contour[0].y + contour[contour.length - 1].y) / 2
};
contour.unshift(p);
contour.push(p);
}
moveTo(contour[0].x, contour[0].y);
for (j = 1, jj = contour.length; j < jj; j++) {
if (contour[j].flags & 1) {
lineTo(contour[j].x, contour[j].y);
} else if (contour[j + 1].flags & 1) {
quadraticCurveTo(contour[j].x, contour[j].y, contour[j + 1].x, contour[j + 1].y);
j++;
} else {
quadraticCurveTo(contour[j].x, contour[j].y, (contour[j].x + contour[j + 1].x) / 2, (contour[j].y + contour[j + 1].y) / 2);
}
}
startPoint = endPoint + 1;
}
}
}
function compileCharString(code, cmds, font) {
var stack = [];
var x = 0,
y = 0;
var stems = 0;
function moveTo(x, y) {
cmds.push({
cmd: 'moveTo',
args: [x, y]
});
}
function lineTo(x, y) {
cmds.push({
cmd: 'lineTo',
args: [x, y]
});
}
function bezierCurveTo(x1, y1, x2, y2, x, y) {
cmds.push({
cmd: 'bezierCurveTo',
args: [x1, y1, x2, y2, x, y]
});
}
function parse(code) {
var i = 0;
while (i < code.length) {
var stackClean = false;
var v = code[i++];
var xa, xb, ya, yb, y1, y2, y3, n, subrCode;
switch (v) {
case 1:
stems += stack.length >> 1;
stackClean = true;
break;
case 3:
stems += stack.length >> 1;
stackClean = true;
break;
case 4:
y += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 5:
while (stack.length > 0) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
break;
case 6:
while (stack.length > 0) {
x += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
y += stack.shift();
lineTo(x, y);
}
break;
case 7:
while (stack.length > 0) {
y += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
x += stack.shift();
lineTo(x, y);
}
break;
case 8:
while (stack.length > 0) {
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 10:
n = stack.pop() + font.subrsBias;
subrCode = font.subrs[n];
if (subrCode) {
parse(subrCode);
}
break;
case 11:
return;
case 12:
v = code[i++];
switch (v) {
case 34:
xa = x + stack.shift();
xb = xa + stack.shift();
y1 = y + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y, xb, y1, x, y1);
xa = x + stack.shift();
xb = xa + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y1, xb, y, x, y);
break;
case 35:
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
stack.pop();
break;
case 36:
xa = x + stack.shift();
y1 = y + stack.shift();
xb = xa + stack.shift();
y2 = y1 + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y1, xb, y2, x, y2);
xa = x + stack.shift();
xb = xa + stack.shift();
y3 = y2 + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y2, xb, y3, x, y);
break;
case 37:
var x0 = x,
y0 = y;
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb;
y = yb;
if (Math.abs(x - x0) > Math.abs(y - y0)) {
x += stack.shift();
} else {
y += stack.shift();
}
bezierCurveTo(xa, ya, xb, yb, x, y);
break;
default:
error('unknown operator: 12 ' + v);
}
break;
case 14:
if (stack.length >= 4) {
var achar = stack.pop();
var bchar = stack.pop();
y = stack.pop();
x = stack.pop();
cmds.push({ cmd: 'save' });
cmds.push({
cmd: 'translate',
args: [x, y]
});
var cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]]));
compileCharString(font.glyphs[cmap.glyphId], cmds, font);
cmds.push({ cmd: 'restore' });
cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]]));
compileCharString(font.glyphs[cmap.glyphId], cmds, font);
}
return;
case 18:
stems += stack.length >> 1;
stackClean = true;
break;
case 19:
stems += stack.length >> 1;
i += stems + 7 >> 3;
stackClean = true;
break;
case 20:
stems += stack.length >> 1;
i += stems + 7 >> 3;
stackClean = true;
break;
case 21:
y += stack.pop();
x += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 22:
x += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 23:
stems += stack.length >> 1;
stackClean = true;
break;
case 24:
while (stack.length > 2) {
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
x += stack.shift();
y += stack.shift();
lineTo(x, y);
break;
case 25:
while (stack.length > 6) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
break;
case 26:
if (stack.length % 2) {
x += stack.shift();
}
while (stack.length > 0) {
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb;
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 27:
if (stack.length % 2) {
y += stack.shift();
}
while (stack.length > 0) {
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb;
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 28:
stack.push((code[i] << 24 | code[i + 1] << 16) >> 16);
i += 2;
break;
case 29:
n = stack.pop() + font.gsubrsBias;
subrCode = font.gsubrs[n];
if (subrCode) {
parse(subrCode);
}
break;
case 30:
while (stack.length > 0) {
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
if (stack.length === 0) {
break;
}
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
y = yb + stack.shift();
x = xb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 31:
while (stack.length > 0) {
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
y = yb + stack.shift();
x = xb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
if (stack.length === 0) {
break;
}
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
default:
if (v < 32) {
error('unknown operator: ' + v);
}
if (v < 247) {
stack.push(v - 139);
} else if (v < 251) {
stack.push((v - 247) * 256 + code[i++] + 108);
} else if (v < 255) {
stack.push(-(v - 251) * 256 - code[i++] - 108);
} else {
stack.push((code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3]) / 65536);
i += 4;
}
break;
}
if (stackClean) {
stack.length = 0;
}
}
}
parse(code);
}
var noop = '';
function CompiledFont(fontMatrix) {
this.compiledGlyphs = Object.create(null);
this.compiledCharCodeToGlyphId = Object.create(null);
this.fontMatrix = fontMatrix;
}
CompiledFont.prototype = {
getPathJs: function getPathJs(unicode) {
var cmap = lookupCmap(this.cmap, unicode);
var fn = this.compiledGlyphs[cmap.glyphId];
if (!fn) {
fn = this.compileGlyph(this.glyphs[cmap.glyphId]);
this.compiledGlyphs[cmap.glyphId] = fn;
}
if (this.compiledCharCodeToGlyphId[cmap.charCode] === undefined) {
this.compiledCharCodeToGlyphId[cmap.charCode] = cmap.glyphId;
}
return fn;
},
compileGlyph: function compileGlyph(code) {
if (!code || code.length === 0 || code[0] === 14) {
return noop;
}
var cmds = [];
cmds.push({ cmd: 'save' });
cmds.push({
cmd: 'transform',
args: this.fontMatrix.slice()
});
cmds.push({
cmd: 'scale',
args: ['size', '-size']
});
this.compileGlyphImpl(code, cmds);
cmds.push({ cmd: 'restore' });
return cmds;
},
compileGlyphImpl: function compileGlyphImpl() {
error('Children classes should implement this.');
},
hasBuiltPath: function hasBuiltPath(unicode) {
var cmap = lookupCmap(this.cmap, unicode);
return this.compiledGlyphs[cmap.glyphId] !== undefined && this.compiledCharCodeToGlyphId[cmap.charCode] !== undefined;
}
};
function TrueTypeCompiled(glyphs, cmap, fontMatrix) {
fontMatrix = fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0];
CompiledFont.call(this, fontMatrix);
this.glyphs = glyphs;
this.cmap = cmap;
}
Util.inherit(TrueTypeCompiled, CompiledFont, {
compileGlyphImpl: function compileGlyphImpl(code, cmds) {
compileGlyf(code, cmds, this);
}
});
function Type2Compiled(cffInfo, cmap, fontMatrix, glyphNameMap) {
fontMatrix = fontMatrix || [0.001, 0, 0, 0.001, 0, 0];
CompiledFont.call(this, fontMatrix);
this.glyphs = cffInfo.glyphs;
this.gsubrs = cffInfo.gsubrs || [];
this.subrs = cffInfo.subrs || [];
this.cmap = cmap;
this.glyphNameMap = glyphNameMap || getGlyphsUnicode();
this.gsubrsBias = this.gsubrs.length < 1240 ? 107 : this.gsubrs.length < 33900 ? 1131 : 32768;
this.subrsBias = this.subrs.length < 1240 ? 107 : this.subrs.length < 33900 ? 1131 : 32768;
}
Util.inherit(Type2Compiled, CompiledFont, {
compileGlyphImpl: function compileGlyphImpl(code, cmds) {
compileCharString(code, cmds, this);
}
});
return {
create: function FontRendererFactory_create(font, seacAnalysisEnabled) {
var data = new Uint8Array(font.data);
var cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm;
var numTables = getUshort(data, 4);
for (var i = 0, p = 12; i < numTables; i++, p += 16) {
var tag = bytesToString(data.subarray(p, p + 4));
var offset = getLong(data, p + 8);
var length = getLong(data, p + 12);
switch (tag) {
case 'cmap':
cmap = parseCmap(data, offset, offset + length);
break;
case 'glyf':
glyf = data.subarray(offset, offset + length);
break;
case 'loca':
loca = data.subarray(offset, offset + length);
break;
case 'head':
unitsPerEm = getUshort(data, offset + 18);
indexToLocFormat = getUshort(data, offset + 50);
break;
case 'CFF ':
cff = parseCff(data, offset, offset + length, seacAnalysisEnabled);
break;
}
}
if (glyf) {
var fontMatrix = !unitsPerEm ? font.fontMatrix : [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0];
return new TrueTypeCompiled(parseGlyfTable(glyf, loca, indexToLocFormat), cmap, fontMatrix);
}
return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
}
};
}();
exports.FontRendererFactory = FontRendererFactory;