Seac support for Windows
This commit is contained in:
parent
3b506bd294
commit
8ee193892b
@ -979,29 +979,45 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
var width = vmetric ? -vmetric[0] : glyph.width;
|
||||
var charWidth = width * fontSize * current.fontMatrix[0] +
|
||||
charSpacing * current.fontDirection;
|
||||
var accent = glyph.accent;
|
||||
|
||||
var scaledX, scaledY, scaledAccentX, scaledAccentY;
|
||||
if (!glyph.disabled) {
|
||||
if (vertical) {
|
||||
var scaledX = vx / fontSizeScale;
|
||||
var scaledY = (x + vy) / fontSizeScale;
|
||||
scaledX = vx / fontSizeScale;
|
||||
scaledY = (x + vy) / fontSizeScale;
|
||||
} else {
|
||||
var scaledX = x / fontSizeScale;
|
||||
var scaledY = 0;
|
||||
scaledX = x / fontSizeScale;
|
||||
scaledY = 0;
|
||||
}
|
||||
if (accent) {
|
||||
scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
|
||||
scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
|
||||
}
|
||||
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:
|
||||
@ -1010,6 +1026,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
if (textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) {
|
||||
var clipCtx = this.getCurrentTextClipping();
|
||||
clipCtx.fillText(character, scaledX, scaledY);
|
||||
if (accent) {
|
||||
clipCtx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
128
src/fonts.js
128
src/fonts.js
@ -34,6 +34,10 @@ var PDF_GLYPH_SPACE_UNITS = 1000;
|
||||
// in tracemonkey and various other pdfs with type1 fonts.
|
||||
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 FontFlags = {
|
||||
@ -2445,6 +2449,7 @@ var Font = (function FontClosure() {
|
||||
this.widths = properties.widths;
|
||||
this.defaultWidth = properties.defaultWidth;
|
||||
this.encoding = properties.baseEncoding;
|
||||
this.seacMap = properties.seacMap;
|
||||
|
||||
this.loading = true;
|
||||
}
|
||||
@ -4167,6 +4172,36 @@ var Font = (function FontClosure() {
|
||||
}
|
||||
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' ||
|
||||
properties.subtype == 'CIDFontType0C')) {
|
||||
var encoding = [];
|
||||
@ -4515,16 +4550,28 @@ var Font = (function FontClosure() {
|
||||
|
||||
var unicodeChars = !('toUnicode' in this) ? charcode :
|
||||
this.toUnicode[charcode] || charcode;
|
||||
if (typeof unicodeChars === 'number')
|
||||
if (typeof unicodeChars === 'number') {
|
||||
unicodeChars = String.fromCharCode(unicodeChars);
|
||||
}
|
||||
|
||||
width = isNum(width) ? width : this.defaultWidth;
|
||||
disabled = this.unicodeIsEnabled ?
|
||||
!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 {
|
||||
fontChar: String.fromCharCode(fontCharCode),
|
||||
unicode: unicodeChars,
|
||||
accent: accent,
|
||||
width: width,
|
||||
vmetric: vmetric,
|
||||
disabled: disabled,
|
||||
@ -4807,7 +4854,12 @@ var Type1CharString = (function Type1CharStringClosure() {
|
||||
case (12 << 8) + 6: // seac
|
||||
// seac is like type 2's special endchar but it doesn't use the
|
||||
// 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);
|
||||
}
|
||||
break;
|
||||
case (12 << 8) + 7: // sbw
|
||||
if (this.stack.length < 4) {
|
||||
@ -5167,6 +5219,7 @@ var Type1Parser = function type1Parser() {
|
||||
program.charstrings.push({
|
||||
glyph: glyph,
|
||||
data: output,
|
||||
seac: charString.seac,
|
||||
lsb: charString.lsb,
|
||||
width: charString.width
|
||||
});
|
||||
@ -5333,6 +5386,7 @@ var Type1Font = function Type1Font(name, file, properties) {
|
||||
this.charstrings = charstrings;
|
||||
this.data = this.wrap(name, type2Charstrings, this.charstrings,
|
||||
subrs, properties);
|
||||
this.seacs = this.getSeacs(data.charstrings);
|
||||
};
|
||||
|
||||
Type1Font.prototype = {
|
||||
@ -5362,6 +5416,18 @@ Type1Font.prototype = {
|
||||
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(
|
||||
type1Charstrings) {
|
||||
var type2Charstrings = [];
|
||||
@ -5517,6 +5583,7 @@ var CFFFont = (function CFFFontClosure() {
|
||||
|
||||
this.charstrings = charstrings;
|
||||
this.glyphIds = glyphIds;
|
||||
this.seacs = cff.seacs;
|
||||
},
|
||||
getCharStrings: function CFFFont_getCharStrings(charsets, encoding) {
|
||||
var charstrings = [];
|
||||
@ -5619,11 +5686,27 @@ var CFFParser = (function CFFParserClosure() {
|
||||
null,
|
||||
null,
|
||||
{ id: 'abs', min: 1, stackDelta: 0 },
|
||||
{ id: 'add', min: 2, stackDelta: -1 },
|
||||
{ id: 'sub', min: 2, stackDelta: -1 },
|
||||
{ id: 'div', min: 2, stackDelta: -1 },
|
||||
{ id: 'add', min: 2, stackDelta: -1,
|
||||
stackFn: function stack_div(stack, index) {
|
||||
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,
|
||||
{ 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 },
|
||||
null,
|
||||
null,
|
||||
@ -5633,7 +5716,11 @@ var CFFParser = (function CFFParserClosure() {
|
||||
{ id: 'get', min: 1, stackDelta: 0 },
|
||||
{ id: 'ifelse', min: 4, stackDelta: -3 },
|
||||
{ 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,
|
||||
{ id: 'sqrt', min: 1, stackDelta: 0 },
|
||||
{ id: 'dup', min: 1, stackDelta: 1 },
|
||||
@ -5681,7 +5768,9 @@ var CFFParser = (function CFFParserClosure() {
|
||||
cff.isCIDFont = topDict.hasName('ROS');
|
||||
|
||||
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');
|
||||
if (fontMatrix) {
|
||||
@ -5892,11 +5981,13 @@ var CFFParser = (function CFFParserClosure() {
|
||||
},
|
||||
parseCharStrings: function CFFParser_parseCharStrings(charStringOffset) {
|
||||
var charStrings = this.parseIndex(charStringOffset).obj;
|
||||
var seacs = [];
|
||||
var count = charStrings.count;
|
||||
for (var i = 0; i < count; i++) {
|
||||
var charstring = charStrings.get(i);
|
||||
|
||||
var stackSize = 0;
|
||||
var stack = [];
|
||||
var undefStack = true;
|
||||
var hints = 0;
|
||||
var valid = true;
|
||||
@ -5920,18 +6011,29 @@ var CFFParser = (function CFFParserClosure() {
|
||||
validationCommand = CharstringValidationData12[q];
|
||||
}
|
||||
} else if (value === 28) { // number (16 bit)
|
||||
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16)) >> 16;
|
||||
j += 2;
|
||||
stackSize++;
|
||||
} else if (value == 14) {
|
||||
if (stackSize >= 4) {
|
||||
stackSize -= 4;
|
||||
if (SEAC_ANALYSIS_ENABLED) {
|
||||
seacs[i] = stack.slice(stackSize, stackSize + 4);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
} else if (value >= 32 && value <= 246) { // number
|
||||
stack[stackSize] = value - 139;
|
||||
stackSize++;
|
||||
} 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++;
|
||||
stackSize++;
|
||||
} 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;
|
||||
stackSize++;
|
||||
} else if (value == 19 || value == 20) {
|
||||
@ -5955,6 +6057,9 @@ var CFFParser = (function CFFParserClosure() {
|
||||
}
|
||||
}
|
||||
if ('stackDelta' in validationCommand) {
|
||||
if ('stackFn' in validationCommand) {
|
||||
validationCommand.stackFn(stack, stackSize);
|
||||
}
|
||||
stackSize += validationCommand.stackDelta;
|
||||
} else if (validationCommand.resetStack) {
|
||||
stackSize = 0;
|
||||
@ -5970,7 +6075,7 @@ var CFFParser = (function CFFParserClosure() {
|
||||
charStrings.set(i, new Uint8Array([14]));
|
||||
}
|
||||
}
|
||||
return charStrings;
|
||||
return { charStrings: charStrings, seacs: seacs };
|
||||
},
|
||||
parsePrivateDict: function CFFParser_parsePrivateDict(parentDict) {
|
||||
// no private dict, do nothing
|
||||
@ -6851,6 +6956,13 @@ var CFFCompiler = (function CFFCompilerClosure() {
|
||||
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
|
||||
// http://code.google.com/p/chromium/issues/detail?id=122465
|
||||
// https://github.com/mozilla/pdf.js/issues/1689
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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';
|
||||
|
||||
@ -113,12 +114,65 @@ describe('font', function() {
|
||||
14 // endchar
|
||||
]);
|
||||
parser.bytes = bytes;
|
||||
var charStrings = parser.parseCharStrings(0);
|
||||
var charStrings = parser.parseCharStrings(0).charStrings;
|
||||
expect(charStrings.count).toEqual(1);
|
||||
// shoudn't be sanitized
|
||||
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() {
|
||||
var charset = parser.parseCharsets(0, 0, null, true);
|
||||
expect(charset.predefined).toEqual(true);
|
||||
|
Loading…
Reference in New Issue
Block a user