Drawing without fillText; refactoring ADD_TO_PATH

This commit is contained in:
Yury Delendik 2013-05-15 15:57:27 -05:00
parent 67eb3bbc68
commit 0e133f0090
7 changed files with 812 additions and 100 deletions

View File

@ -247,6 +247,7 @@ target.bundle = function(args) {
'crypto.js', 'crypto.js',
'evaluator.js', 'evaluator.js',
'fonts.js', 'fonts.js',
'font_renderer.js',
'glyphlist.js', 'glyphlist.js',
'image.js', 'image.js',
'metrics.js', 'metrics.js',

View File

@ -32,6 +32,7 @@ var TextRenderingMode = {
STROKE_ADD_TO_PATH: 5, STROKE_ADD_TO_PATH: 5,
FILL_STROKE_ADD_TO_PATH: 6, FILL_STROKE_ADD_TO_PATH: 6,
ADD_TO_PATH: 7, ADD_TO_PATH: 7,
FILL_STROKE_MASK: 3,
ADD_TO_PATH_FLAG: 4 ADD_TO_PATH_FLAG: 4
}; };
@ -632,10 +633,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.current = old.clone(); this.current = old.clone();
}, },
restore: function CanvasGraphics_restore() { restore: function CanvasGraphics_restore() {
if ('textClipLayers' in this) {
this.completeTextClipping();
}
var prev = this.stateStack.pop(); var prev = this.stateStack.pop();
if (prev) { if (prev) {
this.current = prev; this.current = prev;
@ -783,64 +780,25 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.current.y = this.current.lineY = 0; this.current.y = this.current.lineY = 0;
}, },
endText: function CanvasGraphics_endText() { endText: function CanvasGraphics_endText() {
if ('textClipLayers' in this) { if (!('pendingTextPaths' in this)) {
this.swapImageForTextClipping(); this.ctx.beginPath();
return;
} }
}, var paths = this.pendingTextPaths;
getCurrentTextClipping: function CanvasGraphics_getCurrentTextClipping() {
var ctx = this.ctx; var ctx = this.ctx;
var transform = ctx.mozCurrentTransform;
if ('textClipLayers' in this) { ctx.save();
// we need to reset only font and transform ctx.beginPath();
var maskCtx = this.textClipLayers.maskCtx; for (var i = 0; i < paths.length; i++) {
maskCtx.setTransform.apply(maskCtx, transform); var path = paths[i];
maskCtx.font = ctx.font; ctx.setTransform.apply(ctx, path.transform);
return maskCtx; ctx.translate(path.x, path.y);
path.addToPath(ctx, path.fontSize);
} }
var canvasWidth = ctx.canvas.width;
var canvasHeight = ctx.canvas.height;
// keeping track of the text clipping of the separate canvas
var maskCanvas = createScratchCanvas(canvasWidth, canvasHeight);
var maskCtx = maskCanvas.getContext('2d');
maskCtx.setTransform.apply(maskCtx, transform);
maskCtx.font = ctx.font;
var textClipLayers = {
maskCanvas: maskCanvas,
maskCtx: maskCtx
};
this.textClipLayers = textClipLayers;
return maskCtx;
},
swapImageForTextClipping:
function CanvasGraphics_swapImageForTextClipping() {
var ctx = this.ctx;
var canvasWidth = ctx.canvas.width;
var canvasHeight = ctx.canvas.height;
// saving current image content and clearing whole canvas
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
var data = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
this.textClipLayers.imageData = data;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.restore(); ctx.restore();
}, ctx.clip();
completeTextClipping: function CanvasGraphics_completeTextClipping() { ctx.beginPath();
var ctx = this.ctx; delete this.pendingTextPaths;
// applying mask to the image (result is saved in maskCanvas)
var maskCtx = this.textClipLayers.maskCtx;
maskCtx.setTransform(1, 0, 0, 1, 0, 0);
maskCtx.globalCompositeOperation = 'source-in';
maskCtx.drawImage(ctx.canvas, 0, 0);
// restoring image data and applying the result of masked drawing
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.putImageData(this.textClipLayers.imageData, 0, 0);
ctx.drawImage(this.textClipLayers.maskCanvas, 0, 0);
ctx.restore();
delete this.textClipLayers;
}, },
setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) { setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
this.current.charSpacing = spacing; this.current.charSpacing = spacing;
@ -958,6 +916,59 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
return geometry; return geometry;
}, },
paintChar: function (character, x, y) {
var ctx = this.ctx;
var current = this.current;
var font = current.font;
var fontSize = current.fontSize / current.fontSizeScale;
var textRenderingMode = current.textRenderingMode;
var fillStrokeMode = textRenderingMode &
TextRenderingMode.FILL_STROKE_MASK;
var isAddToPathSet = !!(textRenderingMode &
TextRenderingMode.ADD_TO_PATH_FLAG);
var addToPath;
if (font.disableFontFace || isAddToPathSet) {
addToPath = font.renderer.getPathGenerator(character);
}
if (font.disableFontFace) {
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
addToPath(ctx, fontSize);
if (fillStrokeMode === TextRenderingMode.FILL ||
fillStrokeMode === TextRenderingMode.FILL_STROKE) {
ctx.fill();
}
if (fillStrokeMode === TextRenderingMode.STROKE ||
fillStrokeMode === TextRenderingMode.FILL_STROKE) {
ctx.stroke();
}
ctx.restore();
} else {
if (fillStrokeMode === TextRenderingMode.FILL ||
fillStrokeMode === TextRenderingMode.FILL_STROKE) {
ctx.fillText(character, x, y);
}
if (fillStrokeMode === TextRenderingMode.STROKE ||
fillStrokeMode === TextRenderingMode.FILL_STROKE) {
ctx.strokeText(character, x, y);
}
}
if (isAddToPathSet) {
var paths = this.pendingTextPaths || (this.pendingTextPaths = []);
paths.push({
transform: ctx.mozCurrentTransform,
x: x,
y: y,
fontSize: fontSize,
addToPath: addToPath
});
}
},
showText: function CanvasGraphics_showText(str, skipTextSelection) { showText: function CanvasGraphics_showText(str, skipTextSelection) {
var ctx = this.ctx; var ctx = this.ctx;
var current = this.current; var current = this.current;
@ -973,7 +984,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var textLayer = this.textLayer; var textLayer = this.textLayer;
var geom; var geom;
var textSelection = textLayer && !skipTextSelection ? true : false; var textSelection = textLayer && !skipTextSelection ? true : false;
var textRenderingMode = current.textRenderingMode;
var canvasWidth = 0.0; var canvasWidth = 0.0;
var vertical = font.vertical; var vertical = font.vertical;
var defaultVMetrics = font.defaultVMetrics; var defaultVMetrics = font.defaultVMetrics;
@ -1071,10 +1081,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
scaledX = x / fontSizeScale; scaledX = x / fontSizeScale;
scaledY = 0; scaledY = 0;
} }
if (accent) {
scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
}
if (font.remeasure && width > 0) { if (font.remeasure && width > 0) {
// some standard fonts may not have the exact width, trying to // some standard fonts may not have the exact width, trying to
@ -1091,41 +1097,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} }
} }
switch (textRenderingMode) { this.paintChar(character, scaledX, scaledY);
default: // other unsupported rendering modes if (accent) {
case TextRenderingMode.FILL: scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
case TextRenderingMode.FILL_ADD_TO_PATH: scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
ctx.fillText(character, scaledX, scaledY); this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
if (accent) {
ctx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
}
break;
case TextRenderingMode.STROKE:
case TextRenderingMode.STROKE_ADD_TO_PATH:
ctx.strokeText(character, scaledX, scaledY);
if (accent) {
ctx.strokeText(accent.fontChar, scaledAccentX, scaledAccentY);
}
break;
case TextRenderingMode.FILL_STROKE:
case TextRenderingMode.FILL_STROKE_ADD_TO_PATH:
ctx.fillText(character, scaledX, scaledY);
ctx.strokeText(character, scaledX, scaledY);
if (accent) {
ctx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
ctx.strokeText(accent.fontChar, scaledAccentX, scaledAccentY);
}
break;
case TextRenderingMode.INVISIBLE:
case TextRenderingMode.ADD_TO_PATH:
break;
}
if (textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) {
var clipCtx = this.getCurrentTextClipping();
clipCtx.fillText(character, scaledX, scaledY);
if (accent) {
clipCtx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
}
} }
} }

711
src/font_renderer.js Normal file
View File

@ -0,0 +1,711 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Copyright 2012 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.
*/
/* globals error, Stream, GlyphsUnicode, CFFParser, Encodings */
'use strict';
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);
if (format === 4) {
var length = getUshort(data, start + offset + 2);
var segCount = getUshort(data, start + offset + 6) >> 1;
var p = start + offset + 14;
var ranges = [];
for (var i = 0; i < segCount; i++, p += 2) {
ranges[i] = {end: getUshort(data, p)};
}
p += 2;
for (var i = 0; i < segCount; i++, p += 2) {
ranges[i].start = getUshort(data, p);
}
for (var i = 0; i < segCount; i++, p += 2) {
ranges[i].idDelta = getUshort(data, p);
}
for (var 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) {
var length = getLong(data, start + offset + 4);
var groups = getLong(data, start + offset + 12);
var p = start + offset + 16;
var ranges = [];
for (var 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) {
var properties = {};
var parser = new CFFParser(
new Stream(data, start, end - start), properties);
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);
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) {
return (ranges[l].idDelta + (ranges[l].ids ?
ranges[l].ids[code - ranges[l].start] : code)) & 0xFFFF;
}
return 0;
}
function compileGlyf(code, js, font) {
function moveTo(x, y) {
js.push('c.moveTo(' + x + ',' + y + ');');
}
function lineTo(x, y) {
js.push('c.lineTo(' + x + ',' + y + ');');
}
function quadraticCurveTo(xa, ya, x, y) {
js.push('c.quadraticCurveTo(' + xa + ',' + ya + ',' +
x + ',' + y + ');');
}
var i = 0;
var numberOfContours = ((code[i] << 24) | (code[i + 1] << 16)) >> 16;
var xMin = ((code[i + 2] << 24) | (code[i + 3] << 16)) >> 16;
var yMin = ((code[i + 4] << 24) | (code[i + 5] << 16)) >> 16;
var xMax = ((code[i + 6] << 24) | (code[i + 7] << 16)) >> 16;
var yMax = ((code[i + 8] << 24) | (code[i + 9] << 16)) >> 16;
i += 10;
if (numberOfContours < 0) {
// composite glyph
var x = 0, y = 0;
do {
var 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; // TODO "they are points" ?
}
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) {
js.push('c.save();');
js.push('c.transform(' + scaleX + ',' + scale01 + ',' +
scale10 + ',' + scaleY + ',' + x + ',' + y + ');');
compileGlyf(subglyph, js, font);
js.push('c.restore();');
}
} while ((flags & 0x20));
} else {
// simple glyph
var endPtsOfContours = [];
for (var 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; // skipping the instructions
var numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1;
var points = [];
while (points.length < numberOfPoints) {
var flags = code[i++], repeat = 1;
if ((flags & 0x08)) {
repeat += code[i++];
}
while (repeat-- > 0) {
points.push({flags: flags});
}
}
var x = 0, y = 0;
for (var 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 (var 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 (var i = 0; i < numberOfContours; i++) {
var endPoint = endPtsOfContours[i];
// contours might have implicit points, which is located in the middle
// between two neighboring off-curve points
var contour = points.slice(startPoint, endPoint + 1);
if ((contour[0].flags & 1)) {
contour.push(contour[0]); // using start point at the contour end
} else if ((contour[contour.length - 1].flags & 1)) {
// first is off-curve point, trying to use one from the end
contour.unshift(contour[contour.length - 1]);
} else {
// start and end are off-curve points, creating implicit one
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 (var 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, js, font) {
var stack = [];
var x = 0, y = 0;
var stems = 0;
function moveTo(x, y) {
js.push('c.moveTo(' + x + ',' + y + ');');
}
function lineTo(x, y) {
js.push('c.lineTo(' + x + ',' + y + ');');
}
function bezierCurveTo(x1, y1, x2, y2, x, y) {
js.push('c.bezierCurveTo(' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + ',' +
x + ',' + y + ');');
}
function parse(code) {
var i = 0;
while (i < code.length) {
var stackClean = false;
var v = code[i++];
switch (v) {
case 1: // hstem
stems += stack.length >> 1;
stackClean = true;
break;
case 3: // vstem
stems += stack.length >> 1;
stackClean = true;
break;
case 4: // vmoveto
y += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 5: // rlineto
while (stack.length > 0) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
break;
case 6: // hlineto
while (stack.length > 0) {
x += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
y += stack.shift();
lineTo(x, y);
}
break;
case 7: // vlineto
while (stack.length > 0) {
y += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
x += stack.shift();
lineTo(x, y);
}
break;
case 8: // rrcurveto
while (stack.length > 0) {
var xa = x + stack.shift(), ya = y + stack.shift();
var 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: // callsubr
var n = stack.pop() + font.subrsBias;
var subrCode = font.subrs[n];
if (subrCode) {
parse(subrCode);
}
break;
case 11: // return
return;
case 12:
v = code[i++];
switch (v) {
case 34: // flex
var xa = x + stack.shift();
var xb = xa + stack.shift(), y1 = y + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y, xb, y1, x, y1);
var xa = x + stack.shift();
var xb = xa + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y1, xb, y, x, y);
break;
case 35: // flex
var xa = x + stack.shift(), ya = y + stack.shift();
var xb = xa + stack.shift(), yb = ya + stack.shift();
x = xb + stack.shift(); y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
var xa = x + stack.shift(), ya = y + stack.shift();
var 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(); // fd
break;
case 36: // hflex1
var xa = x + stack.shift(), y1 = y + stack.shift();
var xb = xa + stack.shift(), y2 = y1 + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y1, xb, y2, x, y2);
var xa = x + stack.shift();
var xb = xa + stack.shift(), y3 = y2 + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y2, xb, y3, x, y);
break;
case 37: // flex1
var x0 = x, y0 = y;
var xa = x + stack.shift(), ya = y + stack.shift();
var xb = xa + stack.shift(), yb = ya + stack.shift();
x = xb + stack.shift(); y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
var xa = x + stack.shift(), ya = y + stack.shift();
var 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: // endchar
if (stack.length >= 4) {
var achar = stack.pop();
var bchar = stack.pop();
y = stack.pop();
x = stack.pop();
js.push('c.save();');
js.push('c.translate('+ x + ',' + y + ');');
var gid = lookupCmap(font.cmap, String.fromCharCode(
font.glyphNameMap[Encodings.StandardEncoding[achar]]));
compileCharString(font.glyphs[gid], js, font);
js.push('c.restore();');
gid = lookupCmap(font.cmap, String.fromCharCode(
font.glyphNameMap[Encodings.StandardEncoding[bchar]]));
compileCharString(font.glyphs[gid], js, font);
}
return;
case 18: // hstemhm
stems += stack.length >> 1;
stackClean = true;
break;
case 19: // hintmask
stems += stack.length >> 1;
i += (stems + 7) >> 3;
stackClean = true;
break;
case 20: // cntrmask
stems += stack.length >> 1;
i += (stems + 7) >> 3;
stackClean = true;
break;
case 21: // rmoveto
y += stack.pop();
x += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 22: // hmoveto
x += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 23: // vstemhm
stems += stack.length >> 1;
stackClean = true;
break;
case 24: // rcurveline
while (stack.length > 2) {
var xa = x + stack.shift(), ya = y + stack.shift();
var 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: // rlinecurve
while (stack.length > 6) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
var xa = x + stack.shift(), ya = y + stack.shift();
var 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: // vvcurveto
if (stack.length % 2) {
x += stack.shift();
}
while (stack.length > 0) {
var xa = x, ya = y + stack.shift();
var xb = xa + stack.shift(), yb = ya + stack.shift();
x = xb; y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 27: // hhcurveto
if (stack.length % 2) {
y += stack.shift();
}
while (stack.length > 0) {
var xa = x + stack.shift(), ya = y;
var 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: // callgsubr
var n = stack.pop() + font.gsubrsBias;
var subrCode = font.gsubrs[n];
if (subrCode) {
parse(subrCode);
}
break;
case 30: // vhcurveto
while (stack.length > 0) {
var xa = x, ya = y + stack.shift();
var 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;
}
var xa = x + stack.shift(), ya = y;
var 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: // hvcurveto
while (stack.length > 0) {
var xa = x + stack.shift(), ya = y;
var 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;
}
var xa = x, ya = y + stack.shift();
var 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);
}
function TrueTypeCompiled(glyphs, cmap, fontMatrix) {
this.glyphs = glyphs;
this.cmap = cmap;
this.fontMatrix = fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0];
this.compiledGlyphs = [];
}
var noop = function () {};
TrueTypeCompiled.prototype = {
getPathGenerator: function (unicode) {
var gid = lookupCmap(this.cmap, unicode);
var fn = this.compiledGlyphs[gid];
if (!fn) {
this.compiledGlyphs[gid] = fn = this.compileGlyph(this.glyphs[gid]);
}
return fn;
},
compileGlyph: function (code) {
if (!code || code.length === 0 || code[0] === 14) {
return noop;
}
var js = [];
js.push('c.save();');
js.push('c.transform(' + this.fontMatrix.join(',') + ');');
js.push('c.scale(size, -size);');
var stack = [], x = 0, y = 0;
compileGlyf(code, js, this);
js.push('c.restore();');
/*jshint -W054 */
return new Function('c', 'size', js.join('\n'));
}
};
function Type2Compiled(cffInfo, cmap, fontMatrix, glyphNameMap) {
this.glyphs = cffInfo.glyphs;
this.gsubrs = cffInfo.gsubrs || [];
this.subrs = cffInfo.subrs || [];
this.cmap = cmap;
this.glyphNameMap = glyphNameMap || GlyphsUnicode;
this.fontMatrix = fontMatrix || [0.001, 0, 0, 0.001, 0, 0];
this.compiledGlyphs = [];
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;
}
Type2Compiled.prototype = {
getPathGenerator: function (unicode) {
var gid = lookupCmap(this.cmap, unicode);
var fn = this.compiledGlyphs[gid];
if (!fn) {
this.compiledGlyphs[gid] = fn = this.compileGlyph(this.glyphs[gid]);
}
return fn;
},
compileGlyph: function (code) {
if (!code || code.length === 0 || code[0] === 14) {
return noop;
}
var js = [];
js.push('c.save();');
js.push('c.transform(' + this.fontMatrix.join(',') + ');');
js.push('c.scale(size, -size);');
var stack = [], x = 0, y = 0;
compileCharString(code, js, this);
js.push('c.restore();');
/*jshint -W054 */
return new Function('c', 'size', js.join('\n'));
}
};
return {
create: function FontRendererFactory_create(font) {
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 = String.fromCharCode.apply(null, 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);
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);
} else {
return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
}
}
};
})();

View File

@ -17,7 +17,8 @@
/* globals assert, bytesToString, CIDToUnicodeMaps, error, ExpertCharset, /* globals assert, bytesToString, CIDToUnicodeMaps, error, ExpertCharset,
ExpertSubsetCharset, FileReaderSync, globalScope, GlyphsUnicode, ExpertSubsetCharset, FileReaderSync, globalScope, GlyphsUnicode,
info, isArray, isNum, ISOAdobeCharset, isWorker, PDFJS, Stream, info, isArray, isNum, ISOAdobeCharset, isWorker, PDFJS, Stream,
stringToBytes, TextDecoder, TODO, warn, Lexer, Util */ stringToBytes, TextDecoder, TODO, warn, Lexer, Util, shadow,
FontRendererFactory */
'use strict'; 'use strict';
@ -40,6 +41,8 @@ var SEAC_ANALYSIS_ENABLED = false;
var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
PDFJS.disableFontFace = false;
var FontFlags = { var FontFlags = {
FixedPitch: 1, FixedPitch: 1,
Serif: 2, Serif: 2,
@ -3019,6 +3022,11 @@ var Font = (function FontClosure() {
mimetype: null, mimetype: null,
encoding: null, encoding: null,
get renderer() {
var renderer = FontRendererFactory.create(this);
return shadow(this, 'renderer', renderer);
},
exportData: function Font_exportData() { exportData: function Font_exportData() {
var data = {}; var data = {};
for (var i in this) { for (var i in this) {
@ -4565,6 +4573,11 @@ var Font = (function FontClosure() {
if (!this.data) if (!this.data)
return null; return null;
if (PDFJS.disableFontFace) {
this.disableFontFace = true;
return null;
}
var data = bytesToString(this.data); var data = bytesToString(this.data);
var fontName = this.loadedName; var fontName = this.loadedName;

View File

@ -35,6 +35,7 @@ limitations under the License.
<script type="text/javascript" src="/src/crypto.js"></script> <script type="text/javascript" src="/src/crypto.js"></script>
<script type="text/javascript" src="/src/evaluator.js"></script> <script type="text/javascript" src="/src/evaluator.js"></script>
<script type="text/javascript" src="/src/fonts.js"></script> <script type="text/javascript" src="/src/fonts.js"></script>
<script type="text/javascript" src="/src/font_renderer.js"></script>
<script type="text/javascript" src="/src/glyphlist.js"></script> <script type="text/javascript" src="/src/glyphlist.js"></script>
<script type="text/javascript" src="/src/image.js"></script> <script type="text/javascript" src="/src/image.js"></script>
<script type="text/javascript" src="/src/metrics.js"></script> <script type="text/javascript" src="/src/metrics.js"></script>

View File

@ -55,6 +55,7 @@ limitations under the License.
<script type="text/javascript" src="../src/crypto.js"></script> <script type="text/javascript" src="../src/crypto.js"></script>
<script type="text/javascript" src="../src/evaluator.js"></script> <script type="text/javascript" src="../src/evaluator.js"></script>
<script type="text/javascript" src="../src/fonts.js"></script> <script type="text/javascript" src="../src/fonts.js"></script>
<script type="text/javascript" src="../src/font_renderer.js"></script>
<script type="text/javascript" src="../src/glyphlist.js"></script> <script type="text/javascript" src="../src/glyphlist.js"></script>
<script type="text/javascript" src="../src/image.js"></script> <script type="text/javascript" src="../src/image.js"></script>
<script type="text/javascript" src="../src/metrics.js"></script> <script type="text/javascript" src="../src/metrics.js"></script>

View File

@ -2330,7 +2330,7 @@ var PageView = function pageView(container, id, scale,
//#if (FIREFOX || MOZCENTRAL) //#if (FIREFOX || MOZCENTRAL)
// if (checkIfDocumentFontsUsed && PDFView.pdfDocument.embeddedFontsUsed && // if (checkIfDocumentFontsUsed && PDFView.pdfDocument.embeddedFontsUsed &&
// !PDFView.supportsDocumentFonts) { // PDFJS.disableFontFace) {
// console.error(mozL10n.get('web_fonts_disabled', null, // console.error(mozL10n.get('web_fonts_disabled', null,
// 'Web fonts are disabled: unable to use embedded PDF fonts.')); // 'Web fonts are disabled: unable to use embedded PDF fonts.'));
// PDFView.fallback(); // PDFView.fallback();
@ -3107,11 +3107,20 @@ document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) {
PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true'); PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true');
} }
if ('disableFontFace' in hashParams) {
PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true');
}
//#if !(FIREFOX || MOZCENTRAL) //#if !(FIREFOX || MOZCENTRAL)
var locale = navigator.language; var locale = navigator.language;
if ('locale' in hashParams) if ('locale' in hashParams)
locale = hashParams['locale']; locale = hashParams['locale'];
mozL10n.setLanguage(locale); mozL10n.setLanguage(locale);
//#endif
//#if (FIREFOX || MOZCENTRAL)
//if (!PDFView.supportsDocumentFonts) {
// PDFJS.disableFontFace = true;
//}
//#endif //#endif
if ('textLayer' in hashParams) { if ('textLayer' in hashParams) {