Seac support for Windows

This commit is contained in:
Yury Delendik 2013-02-26 12:00:20 -06:00
parent 3b506bd294
commit 8ee193892b
3 changed files with 200 additions and 15 deletions

View File

@ -979,29 +979,45 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var width = vmetric ? -vmetric[0] : glyph.width; var width = vmetric ? -vmetric[0] : glyph.width;
var charWidth = width * fontSize * current.fontMatrix[0] + var charWidth = width * fontSize * current.fontMatrix[0] +
charSpacing * current.fontDirection; charSpacing * current.fontDirection;
var accent = glyph.accent;
var scaledX, scaledY, scaledAccentX, scaledAccentY;
if (!glyph.disabled) { if (!glyph.disabled) {
if (vertical) { if (vertical) {
var scaledX = vx / fontSizeScale; scaledX = vx / fontSizeScale;
var scaledY = (x + vy) / fontSizeScale; scaledY = (x + vy) / fontSizeScale;
} else { } else {
var scaledX = x / fontSizeScale; scaledX = x / fontSizeScale;
var scaledY = 0; scaledY = 0;
}
if (accent) {
scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
} }
switch (textRenderingMode) { switch (textRenderingMode) {
default: // other unsupported rendering modes default: // other unsupported rendering modes
case TextRenderingMode.FILL: case TextRenderingMode.FILL:
case TextRenderingMode.FILL_ADD_TO_PATH: case TextRenderingMode.FILL_ADD_TO_PATH:
ctx.fillText(character, scaledX, scaledY); ctx.fillText(character, scaledX, scaledY);
if (accent) {
ctx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
}
break; break;
case TextRenderingMode.STROKE: case TextRenderingMode.STROKE:
case TextRenderingMode.STROKE_ADD_TO_PATH: case TextRenderingMode.STROKE_ADD_TO_PATH:
ctx.strokeText(character, scaledX, scaledY); ctx.strokeText(character, scaledX, scaledY);
if (accent) {
ctx.strokeText(accent.fontChar, scaledAccentX, scaledAccentY);
}
break; break;
case TextRenderingMode.FILL_STROKE: case TextRenderingMode.FILL_STROKE:
case TextRenderingMode.FILL_STROKE_ADD_TO_PATH: case TextRenderingMode.FILL_STROKE_ADD_TO_PATH:
ctx.fillText(character, scaledX, scaledY); ctx.fillText(character, scaledX, scaledY);
ctx.strokeText(character, scaledX, scaledY); ctx.strokeText(character, scaledX, scaledY);
if (accent) {
ctx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
ctx.strokeText(accent.fontChar, scaledAccentX, scaledAccentY);
}
break; break;
case TextRenderingMode.INVISIBLE: case TextRenderingMode.INVISIBLE:
case TextRenderingMode.ADD_TO_PATH: case TextRenderingMode.ADD_TO_PATH:
@ -1010,6 +1026,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
if (textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) { if (textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) {
var clipCtx = this.getCurrentTextClipping(); var clipCtx = this.getCurrentTextClipping();
clipCtx.fillText(character, scaledX, scaledY); clipCtx.fillText(character, scaledX, scaledY);
if (accent) {
clipCtx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
}
} }
} }

View File

@ -34,6 +34,10 @@ var PDF_GLYPH_SPACE_UNITS = 1000;
// in tracemonkey and various other pdfs with type1 fonts. // in tracemonkey and various other pdfs with type1 fonts.
var HINTING_ENABLED = false; var HINTING_ENABLED = false;
// Accented charactars are not displayed properly on windows, using this flag
// to control analysis of seac charstrings.
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];
var FontFlags = { var FontFlags = {
@ -2445,6 +2449,7 @@ var Font = (function FontClosure() {
this.widths = properties.widths; this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth; this.defaultWidth = properties.defaultWidth;
this.encoding = properties.baseEncoding; this.encoding = properties.baseEncoding;
this.seacMap = properties.seacMap;
this.loading = true; this.loading = true;
} }
@ -4167,6 +4172,36 @@ var Font = (function FontClosure() {
} }
this.glyphNameMap = glyphNameMap; this.glyphNameMap = glyphNameMap;
var seacs = font.seacs;
if (SEAC_ANALYSIS_ENABLED && seacs) {
var seacMap = [];
var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
for (var i = 0; i < charstrings.length; ++i) {
var charstring = charstrings[i];
var seac = seacs[charstring.gid];
if (!seac) {
continue;
}
var baseGlyphName = Encodings.StandardEncoding[seac[2]];
var baseUnicode = glyphNameMap[baseGlyphName];
var accentGlyphName = Encodings.StandardEncoding[seac[3]];
var accentUnicode = glyphNameMap[accentGlyphName];
if (!baseUnicode || !accentUnicode) {
continue;
}
var accentOffset = {
x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5]
};
seacMap[charstring.unicode] = {
baseUnicode: baseUnicode,
accentUnicode: accentUnicode,
accentOffset: accentOffset
};
}
properties.seacMap = seacMap;
}
if (!properties.hasEncoding && (properties.subtype == 'Type1C' || if (!properties.hasEncoding && (properties.subtype == 'Type1C' ||
properties.subtype == 'CIDFontType0C')) { properties.subtype == 'CIDFontType0C')) {
var encoding = []; var encoding = [];
@ -4515,16 +4550,28 @@ var Font = (function FontClosure() {
var unicodeChars = !('toUnicode' in this) ? charcode : var unicodeChars = !('toUnicode' in this) ? charcode :
this.toUnicode[charcode] || charcode; this.toUnicode[charcode] || charcode;
if (typeof unicodeChars === 'number') if (typeof unicodeChars === 'number') {
unicodeChars = String.fromCharCode(unicodeChars); unicodeChars = String.fromCharCode(unicodeChars);
}
width = isNum(width) ? width : this.defaultWidth; width = isNum(width) ? width : this.defaultWidth;
disabled = this.unicodeIsEnabled ? disabled = this.unicodeIsEnabled ?
!this.unicodeIsEnabled[fontCharCode] : false; !this.unicodeIsEnabled[fontCharCode] : false;
var accent = null;
if (this.seacMap && this.seacMap[fontCharCode]) {
var seac = this.seacMap[fontCharCode];
fontCharCode = seac.baseUnicode;
accent = {
fontChar: String.fromCharCode(seac.accentUnicode),
offset: seac.accentOffset
};
}
return { return {
fontChar: String.fromCharCode(fontCharCode), fontChar: String.fromCharCode(fontCharCode),
unicode: unicodeChars, unicode: unicodeChars,
accent: accent,
width: width, width: width,
vmetric: vmetric, vmetric: vmetric,
disabled: disabled, disabled: disabled,
@ -4807,7 +4854,12 @@ var Type1CharString = (function Type1CharStringClosure() {
case (12 << 8) + 6: // seac case (12 << 8) + 6: // seac
// seac is like type 2's special endchar but it doesn't use the // seac is like type 2's special endchar but it doesn't use the
// first argument asb, so remove it. // first argument asb, so remove it.
if (SEAC_ANALYSIS_ENABLED) {
this.seac = this.stack.splice(-4, 4);
error = this.executeCommand(0, COMMAND_MAP.endchar);
} else {
error = this.executeCommand(4, COMMAND_MAP.endchar); error = this.executeCommand(4, COMMAND_MAP.endchar);
}
break; break;
case (12 << 8) + 7: // sbw case (12 << 8) + 7: // sbw
if (this.stack.length < 4) { if (this.stack.length < 4) {
@ -5167,6 +5219,7 @@ var Type1Parser = function type1Parser() {
program.charstrings.push({ program.charstrings.push({
glyph: glyph, glyph: glyph,
data: output, data: output,
seac: charString.seac,
lsb: charString.lsb, lsb: charString.lsb,
width: charString.width width: charString.width
}); });
@ -5333,6 +5386,7 @@ var Type1Font = function Type1Font(name, file, properties) {
this.charstrings = charstrings; this.charstrings = charstrings;
this.data = this.wrap(name, type2Charstrings, this.charstrings, this.data = this.wrap(name, type2Charstrings, this.charstrings,
subrs, properties); subrs, properties);
this.seacs = this.getSeacs(data.charstrings);
}; };
Type1Font.prototype = { Type1Font.prototype = {
@ -5362,6 +5416,18 @@ Type1Font.prototype = {
return charstrings; return charstrings;
}, },
getSeacs: function Type1Font_getSeacs(charstrings) {
var i, ii;
var seacMap = [];
for (i = 0, ii = charstrings.length; i < ii; i++) {
var charstring = charstrings[i];
if (charstring.seac) {
seacMap[i] = charstring.seac;
}
}
return seacMap;
},
getType2Charstrings: function Type1Font_getType2Charstrings( getType2Charstrings: function Type1Font_getType2Charstrings(
type1Charstrings) { type1Charstrings) {
var type2Charstrings = []; var type2Charstrings = [];
@ -5517,6 +5583,7 @@ var CFFFont = (function CFFFontClosure() {
this.charstrings = charstrings; this.charstrings = charstrings;
this.glyphIds = glyphIds; this.glyphIds = glyphIds;
this.seacs = cff.seacs;
}, },
getCharStrings: function CFFFont_getCharStrings(charsets, encoding) { getCharStrings: function CFFFont_getCharStrings(charsets, encoding) {
var charstrings = []; var charstrings = [];
@ -5619,11 +5686,27 @@ var CFFParser = (function CFFParserClosure() {
null, null,
null, null,
{ id: 'abs', min: 1, stackDelta: 0 }, { id: 'abs', min: 1, stackDelta: 0 },
{ id: 'add', min: 2, stackDelta: -1 }, { id: 'add', min: 2, stackDelta: -1,
{ id: 'sub', min: 2, stackDelta: -1 }, stackFn: function stack_div(stack, index) {
{ id: 'div', min: 2, stackDelta: -1 }, stack[index - 2] = stack[index - 2] + stack[index - 1];
}
},
{ id: 'sub', min: 2, stackDelta: -1,
stackFn: function stack_div(stack, index) {
stack[index - 2] = stack[index - 2] - stack[index - 1];
}
},
{ id: 'div', min: 2, stackDelta: -1,
stackFn: function stack_div(stack, index) {
stack[index - 2] = stack[index - 2] / stack[index - 1];
}
},
null, null,
{ id: 'neg', min: 1, stackDelta: 0 }, { id: 'neg', min: 1, stackDelta: 0,
stackFn: function stack_div(stack, index) {
stack[index - 1] = -stack[index - 1];
}
},
{ id: 'eq', min: 2, stackDelta: -1 }, { id: 'eq', min: 2, stackDelta: -1 },
null, null,
null, null,
@ -5633,7 +5716,11 @@ var CFFParser = (function CFFParserClosure() {
{ id: 'get', min: 1, stackDelta: 0 }, { id: 'get', min: 1, stackDelta: 0 },
{ id: 'ifelse', min: 4, stackDelta: -3 }, { id: 'ifelse', min: 4, stackDelta: -3 },
{ id: 'random', min: 0, stackDelta: 1 }, { id: 'random', min: 0, stackDelta: 1 },
{ id: 'mul', min: 2, stackDelta: -1 }, { id: 'mul', min: 2, stackDelta: -1,
stackFn: function stack_div(stack, index) {
stack[index - 2] = stack[index - 2] * stack[index - 1];
}
},
null, null,
{ id: 'sqrt', min: 1, stackDelta: 0 }, { id: 'sqrt', min: 1, stackDelta: 0 },
{ id: 'dup', min: 1, stackDelta: 1 }, { id: 'dup', min: 1, stackDelta: 1 },
@ -5681,7 +5768,9 @@ var CFFParser = (function CFFParserClosure() {
cff.isCIDFont = topDict.hasName('ROS'); cff.isCIDFont = topDict.hasName('ROS');
var charStringOffset = topDict.getByName('CharStrings'); var charStringOffset = topDict.getByName('CharStrings');
cff.charStrings = this.parseCharStrings(charStringOffset); var charStringsAndSeacs = this.parseCharStrings(charStringOffset);
cff.charStrings = charStringsAndSeacs.charStrings;
cff.seacs = charStringsAndSeacs.seacs;
var fontMatrix = topDict.getByName('FontMatrix'); var fontMatrix = topDict.getByName('FontMatrix');
if (fontMatrix) { if (fontMatrix) {
@ -5892,11 +5981,13 @@ var CFFParser = (function CFFParserClosure() {
}, },
parseCharStrings: function CFFParser_parseCharStrings(charStringOffset) { parseCharStrings: function CFFParser_parseCharStrings(charStringOffset) {
var charStrings = this.parseIndex(charStringOffset).obj; var charStrings = this.parseIndex(charStringOffset).obj;
var seacs = [];
var count = charStrings.count; var count = charStrings.count;
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
var charstring = charStrings.get(i); var charstring = charStrings.get(i);
var stackSize = 0; var stackSize = 0;
var stack = [];
var undefStack = true; var undefStack = true;
var hints = 0; var hints = 0;
var valid = true; var valid = true;
@ -5920,18 +6011,29 @@ var CFFParser = (function CFFParserClosure() {
validationCommand = CharstringValidationData12[q]; validationCommand = CharstringValidationData12[q];
} }
} else if (value === 28) { // number (16 bit) } else if (value === 28) { // number (16 bit)
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16)) >> 16;
j += 2; j += 2;
stackSize++; stackSize++;
} else if (value == 14) { } else if (value == 14) {
if (stackSize >= 4) { if (stackSize >= 4) {
stackSize -= 4; stackSize -= 4;
if (SEAC_ANALYSIS_ENABLED) {
seacs[i] = stack.slice(stackSize, stackSize + 4);
valid = false;
}
} }
} else if (value >= 32 && value <= 246) { // number } else if (value >= 32 && value <= 246) { // number
stack[stackSize] = value - 139;
stackSize++; stackSize++;
} else if (value >= 247 && value <= 254) { // number (+1 bytes) } else if (value >= 247 && value <= 254) { // number (+1 bytes)
stack[stackSize] = value < 251 ?
((value - 247) << 8) + data[j] + 108 :
-((value - 251) << 8) - data[j] - 108;
j++; j++;
stackSize++; stackSize++;
} else if (value == 255) { // number (32 bit) } else if (value == 255) { // number (32 bit)
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16) |
(data[j + 2] << 8) | data[j + 3]) / 65536;
j += 4; j += 4;
stackSize++; stackSize++;
} else if (value == 19 || value == 20) { } else if (value == 19 || value == 20) {
@ -5955,6 +6057,9 @@ var CFFParser = (function CFFParserClosure() {
} }
} }
if ('stackDelta' in validationCommand) { if ('stackDelta' in validationCommand) {
if ('stackFn' in validationCommand) {
validationCommand.stackFn(stack, stackSize);
}
stackSize += validationCommand.stackDelta; stackSize += validationCommand.stackDelta;
} else if (validationCommand.resetStack) { } else if (validationCommand.resetStack) {
stackSize = 0; stackSize = 0;
@ -5970,7 +6075,7 @@ var CFFParser = (function CFFParserClosure() {
charStrings.set(i, new Uint8Array([14])); charStrings.set(i, new Uint8Array([14]));
} }
} }
return charStrings; return { charStrings: charStrings, seacs: seacs };
}, },
parsePrivateDict: function CFFParser_parsePrivateDict(parentDict) { parsePrivateDict: function CFFParser_parsePrivateDict(parentDict) {
// no private dict, do nothing // no private dict, do nothing
@ -6851,6 +6956,13 @@ var CFFCompiler = (function CFFCompilerClosure() {
return CFFCompiler; return CFFCompiler;
})(); })();
// Workaround for seac on Windows.
(function checkSeacSupport() {
if (/Windows/.test(navigator.userAgent)) {
SEAC_ANALYSIS_ENABLED = true;
}
})();
// Workaround for Private Use Area characters in Chrome on Windows // Workaround for Private Use Area characters in Chrome on Windows
// http://code.google.com/p/chromium/issues/detail?id=122465 // http://code.google.com/p/chromium/issues/detail?id=122465
// https://github.com/mozilla/pdf.js/issues/1689 // https://github.com/mozilla/pdf.js/issues/1689

View File

@ -1,6 +1,7 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* globals expect, it, describe, CFFCompiler, CFFParser, CFFIndex, CFFStrings */ /* globals expect, it, describe, CFFCompiler, CFFParser, CFFIndex, CFFStrings,
SEAC_ANALYSIS_ENABLED:true */
'use strict'; 'use strict';
@ -113,12 +114,65 @@ describe('font', function() {
14 // endchar 14 // endchar
]); ]);
parser.bytes = bytes; parser.bytes = bytes;
var charStrings = parser.parseCharStrings(0); var charStrings = parser.parseCharStrings(0).charStrings;
expect(charStrings.count).toEqual(1); expect(charStrings.count).toEqual(1);
// shoudn't be sanitized // shoudn't be sanitized
expect(charStrings.get(0).length).toEqual(38); expect(charStrings.get(0).length).toEqual(38);
}); });
it('parses a CharString endchar with 4 args w/seac enabled', function() {
var seacAnalysisState = SEAC_ANALYSIS_ENABLED;
try {
SEAC_ANALYSIS_ENABLED = true;
var bytes = new Uint8Array([0, 1, // count
1, // offsetSize
0, // offset[0]
237, 247, 22, 247, 72, 204, 247, 86, 14]);
parser.bytes = bytes;
var result = parser.parseCharStrings(0);
expect(result.charStrings.count).toEqual(1);
expect(result.charStrings.get(0).length).toEqual(1);
expect(result.seacs.length).toEqual(1);
expect(result.seacs[0].length).toEqual(4);
expect(result.seacs[0][0]).toEqual(130);
expect(result.seacs[0][1]).toEqual(180);
expect(result.seacs[0][2]).toEqual(65);
expect(result.seacs[0][3]).toEqual(194);
} finally {
SEAC_ANALYSIS_ENABLED = seacAnalysisState;
}
});
it('parses a CharString endchar with 4 args w/seac disabled', function() {
var seacAnalysisState = SEAC_ANALYSIS_ENABLED;
try {
SEAC_ANALYSIS_ENABLED = false;
var bytes = new Uint8Array([0, 1, // count
1, // offsetSize
0, // offset[0]
237, 247, 22, 247, 72, 204, 247, 86, 14]);
parser.bytes = bytes;
var result = parser.parseCharStrings(0);
expect(result.charStrings.count).toEqual(1);
expect(result.charStrings.get(0).length).toEqual(9);
expect(result.seacs.length).toEqual(0);
} finally {
SEAC_ANALYSIS_ENABLED = seacAnalysisState;
}
});
it('parses a CharString endchar no args', function() {
var bytes = new Uint8Array([0, 1, // count
1, // offsetSize
0, // offset[0]
14]);
parser.bytes = bytes;
var result = parser.parseCharStrings(0);
expect(result.charStrings.count).toEqual(1);
expect(result.charStrings.get(0)[0]).toEqual(14);
expect(result.seacs.length).toEqual(0);
});
it('parses predefined charsets', function() { it('parses predefined charsets', function() {
var charset = parser.parseCharsets(0, 0, null, true); var charset = parser.parseCharsets(0, 0, null, true);
expect(charset.predefined).toEqual(true); expect(charset.predefined).toEqual(true);