diff --git a/make.js b/make.js index fbdd137ccdeb5..68c264c505830 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 07b95ec8d44c4..b9abe67ab1c40 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 }; @@ -632,10 +633,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; @@ -783,64 +780,25 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.current.y = this.current.lineY = 0; }, endText: function CanvasGraphics_endText() { - if ('textClipLayers' in this) { - this.swapImageForTextClipping(); - } - }, - getCurrentTextClipping: function CanvasGraphics_getCurrentTextClipping() { - 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; + if (!('pendingTextPaths' in this)) { + this.ctx.beginPath(); + return; } - - 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 paths = this.pendingTextPaths; 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(); - }, - 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.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(); - - delete this.textClipLayers; + ctx.clip(); + ctx.beginPath(); + delete this.pendingTextPaths; }, setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) { this.current.charSpacing = spacing; @@ -958,6 +916,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; @@ -973,7 +984,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; @@ -1071,10 +1081,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 @@ -1091,41 +1097,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 0000000000000..7875c7ab71b4a --- /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 e86be2d20b481..600c7566c7b45 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) { @@ -4565,6 +4573,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 0424f78179ee6..c0e4b720319a2 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -35,6 +35,7 @@ + diff --git a/web/viewer.html b/web/viewer.html index 6f515729e4ed0..e6a96b4064047 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -55,6 +55,7 @@ + diff --git a/web/viewer.js b/web/viewer.js index 9833876939da3..0c3e7ff532e71 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -2330,7 +2330,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(); @@ -3107,11 +3107,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) {