diff --git a/make.js b/make.js
index fbdd137cc..68c264c50 100644
--- a/make.js
+++ b/make.js
@@ -247,6 +247,7 @@ target.bundle = function(args) {
'crypto.js',
'evaluator.js',
'fonts.js',
+ 'font_renderer.js',
'glyphlist.js',
'image.js',
'metrics.js',
diff --git a/src/canvas.js b/src/canvas.js
index 468bcbe62..2a19594ef 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -32,6 +32,7 @@ var TextRenderingMode = {
STROKE_ADD_TO_PATH: 5,
FILL_STROKE_ADD_TO_PATH: 6,
ADD_TO_PATH: 7,
+ FILL_STROKE_MASK: 3,
ADD_TO_PATH_FLAG: 4
};
@@ -786,10 +787,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.current = old.clone();
},
restore: function CanvasGraphics_restore() {
- if ('textClipLayers' in this) {
- this.completeTextClipping();
- }
-
var prev = this.stateStack.pop();
if (prev) {
this.current = prev;
@@ -937,64 +934,25 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.current.y = this.current.lineY = 0;
},
endText: function CanvasGraphics_endText() {
- if ('textClipLayers' in this) {
- this.swapImageForTextClipping();
+ if (!('pendingTextPaths' in this)) {
+ this.ctx.beginPath();
+ return;
}
- },
- getCurrentTextClipping: function CanvasGraphics_getCurrentTextClipping() {
+ var paths = this.pendingTextPaths;
var ctx = this.ctx;
- var transform = ctx.mozCurrentTransform;
- if ('textClipLayers' in this) {
- // we need to reset only font and transform
- var maskCtx = this.textClipLayers.maskCtx;
- maskCtx.setTransform.apply(maskCtx, transform);
- maskCtx.font = ctx.font;
- return maskCtx;
- }
- 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.beginPath();
+ for (var i = 0; i < paths.length; i++) {
+ var path = paths[i];
+ ctx.setTransform.apply(ctx, path.transform);
+ ctx.translate(path.x, path.y);
+ path.addToPath(ctx, path.fontSize);
+ }
ctx.restore();
- },
- completeTextClipping: function CanvasGraphics_completeTextClipping() {
- var ctx = this.ctx;
- // 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;
+ ctx.clip();
+ ctx.beginPath();
+ delete this.pendingTextPaths;
},
setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
this.current.charSpacing = spacing;
@@ -1112,6 +1070,59 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
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) {
var ctx = this.ctx;
var current = this.current;
@@ -1127,7 +1138,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var textLayer = this.textLayer;
var geom;
var textSelection = textLayer && !skipTextSelection ? true : false;
- var textRenderingMode = current.textRenderingMode;
var canvasWidth = 0.0;
var vertical = font.vertical;
var defaultVMetrics = font.defaultVMetrics;
@@ -1227,10 +1237,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
scaledX = x / fontSizeScale;
scaledY = 0;
}
- if (accent) {
- scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
- scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
- }
if (font.remeasure && width > 0) {
// some standard fonts may not have the exact width, trying to
@@ -1247,41 +1253,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}
}
- switch (textRenderingMode) {
- default: // other unsupported rendering modes
- case TextRenderingMode.FILL:
- case TextRenderingMode.FILL_ADD_TO_PATH:
- ctx.fillText(character, scaledX, scaledY);
- 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);
- }
+ this.paintChar(character, scaledX, scaledY);
+ if (accent) {
+ scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
+ scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
+ this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
}
}
diff --git a/src/font_renderer.js b/src/font_renderer.js
new file mode 100644
index 000000000..7875c7ab7
--- /dev/null
+++ b/src/font_renderer.js
@@ -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);
+ }
+ }
+ };
+})();
+
diff --git a/src/fonts.js b/src/fonts.js
index 22d2a1cd9..8803323a5 100644
--- a/src/fonts.js
+++ b/src/fonts.js
@@ -17,7 +17,8 @@
/* globals assert, bytesToString, CIDToUnicodeMaps, error, ExpertCharset,
ExpertSubsetCharset, FileReaderSync, globalScope, GlyphsUnicode,
info, isArray, isNum, ISOAdobeCharset, isWorker, PDFJS, Stream,
- stringToBytes, TextDecoder, TODO, warn, Lexer, Util */
+ stringToBytes, TextDecoder, TODO, warn, Lexer, Util, shadow,
+ FontRendererFactory */
'use strict';
@@ -40,6 +41,8 @@ var SEAC_ANALYSIS_ENABLED = false;
var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
+PDFJS.disableFontFace = false;
+
var FontFlags = {
FixedPitch: 1,
Serif: 2,
@@ -3019,6 +3022,11 @@ var Font = (function FontClosure() {
mimetype: null,
encoding: null,
+ get renderer() {
+ var renderer = FontRendererFactory.create(this);
+ return shadow(this, 'renderer', renderer);
+ },
+
exportData: function Font_exportData() {
var data = {};
for (var i in this) {
@@ -4568,6 +4576,11 @@ var Font = (function FontClosure() {
if (!this.data)
return null;
+ if (PDFJS.disableFontFace) {
+ this.disableFontFace = true;
+ return null;
+ }
+
var data = bytesToString(this.data);
var fontName = this.loadedName;
diff --git a/test/test_slave.html b/test/test_slave.html
index 0424f7817..c0e4b7203 100644
--- a/test/test_slave.html
+++ b/test/test_slave.html
@@ -35,6 +35,7 @@ limitations under the License.
+
diff --git a/web/viewer.html b/web/viewer.html
index 6f515729e..e6a96b406 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -55,6 +55,7 @@ limitations under the License.
+
diff --git a/web/viewer.js b/web/viewer.js
index c7f5efbe9..7e171c521 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -2563,7 +2563,7 @@ var PageView = function pageView(container, id, scale,
//#if (FIREFOX || MOZCENTRAL)
// if (checkIfDocumentFontsUsed && PDFView.pdfDocument.embeddedFontsUsed &&
-// !PDFView.supportsDocumentFonts) {
+// PDFJS.disableFontFace) {
// console.error(mozL10n.get('web_fonts_disabled', null,
// 'Web fonts are disabled: unable to use embedded PDF fonts.'));
// PDFView.fallback();
@@ -3340,11 +3340,20 @@ document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) {
PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true');
}
+ if ('disableFontFace' in hashParams) {
+ PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true');
+ }
+
//#if !(FIREFOX || MOZCENTRAL)
var locale = navigator.language;
if ('locale' in hashParams)
locale = hashParams['locale'];
mozL10n.setLanguage(locale);
+//#endif
+//#if (FIREFOX || MOZCENTRAL)
+//if (!PDFView.supportsDocumentFonts) {
+// PDFJS.disableFontFace = true;
+//}
//#endif
if ('textLayer' in hashParams) {