From 5f810177268b2882f659fc5115d1c7f024bc34d7 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 3 Oct 2011 16:36:01 -0700 Subject: [PATCH] #502 Adding basic Type3 font support. --- fonts.js | 9 +- pdf.js | 171 +++++++++++++++++++++++----------- test/pdfs/simpletype3font.pdf | 141 ++++++++++++++++++++++++++++ test/test_manifest.json | 6 ++ 4 files changed, 269 insertions(+), 58 deletions(-) create mode 100644 test/pdfs/simpletype3font.pdf diff --git a/fonts.js b/fonts.js index 28242f7ca..706f8ef6e 100644 --- a/fonts.js +++ b/fonts.js @@ -414,6 +414,8 @@ var Font = (function Font() { var constructor = function font_constructor(name, file, properties) { this.name = name; this.encoding = properties.encoding; + this.coded = properties.coded; + this.resources = properties.resources; this.sizes = []; var names = name.split('+'); @@ -428,6 +430,9 @@ var Font = (function Font() { this.loading = false; return; } + this.fontMatrix = properties.fontMatrix; + if (properties.type == 'Type3') + return; if (!file) { // The file data is not specified. Trying to fix the font name @@ -478,7 +483,7 @@ var Font = (function Font() { this.data = data; this.type = type; - this.textMatrix = properties.textMatrix; + this.fontMatrix = properties.fontMatrix; this.defaultWidth = properties.defaultWidth; this.loadedName = getUniqueName(); this.composite = properties.composite; @@ -1929,7 +1934,7 @@ var Type1Parser = function type1Parser() { // Make the angle into the right direction matrix[2] *= -1; - properties.textMatrix = matrix; + properties.fontMatrix = matrix; break; case '/Encoding': var size = parseInt(getToken(), 10); diff --git a/pdf.js b/pdf.js index 449fd9c16..18368aa0b 100644 --- a/pdf.js +++ b/pdf.js @@ -4498,6 +4498,7 @@ var PartialEvaluator = (function partialEvaluator() { baseEncoding = Encodings.WinAnsiEncoding.slice(); break; case 'Type1': + case 'Type3': baseEncoding = Encodings.StandardEncoding.slice(); break; default: @@ -4684,35 +4685,43 @@ var PartialEvaluator = (function partialEvaluator() { composite = true; } - // Before PDF 1.5 if the font was one of the base 14 fonts, having a - // FontDescriptor was not required. - // This case is here for compatibility. var descriptor = xref.fetchIfRef(dict.get('FontDescriptor')); if (!descriptor) { - var baseFontName = dict.get('BaseFont'); - if (!IsName(baseFontName)) - return null; + if (type.name == 'Type3') { + // FontDescriptor is only required for Type3 fonts when the document + // is a tagged pdf. Create a barbebones one to get by. + descriptor = new Dict(); + descriptor.set('FontName', new Name(type.name)); + } else { + // Before PDF 1.5 if the font was one of the base 14 fonts, having a + // FontDescriptor was not required. + // This case is here for compatibility. + var baseFontName = dict.get('BaseFont'); + if (!IsName(baseFontName)) + return null; - // Using base font name as a font name. - baseFontName = baseFontName.name.replace(/,/g, '_'); - var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName); + // Using base font name as a font name. + baseFontName = baseFontName.name.replace(/,/g, '_'); + var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName); - var properties = { - type: type.name, - encoding: metricsAndMap.map, - differences: [], - widths: metricsAndMap.widths, - defaultWidth: metricsAndMap.defaultWidth, - firstChar: 0, - lastChar: 256 - }; - this.extractEncoding(dict, xref, properties); + var properties = { + type: type.name, + encoding: metricsAndMap.map, + differences: [], + widths: metricsAndMap.widths, + defaultWidth: metricsAndMap.defaultWidth, + firstChar: 0, + lastChar: 256 + }; + this.extractEncoding(dict, xref, properties); + + return { + name: baseFontName, + dict: baseDict, + properties: properties + }; + } - return { - name: baseFontName, - dict: baseDict, - properties: properties - }; } // According to the spec if 'FontDescriptor' is declared, 'FirstChar', @@ -4771,7 +4780,7 @@ var PartialEvaluator = (function partialEvaluator() { length2: length2, composite: composite, fixedPitch: false, - textMatrix: IDENTITY_MATRIX, + fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX, firstChar: firstChar || 0, lastChar: lastChar || 256, bbox: descriptor.get('FontBBox'), @@ -4784,10 +4793,24 @@ var PartialEvaluator = (function partialEvaluator() { italicAngle: descriptor.get('ItalicAngle'), differences: [], widths: glyphWidths, - encoding: encoding + encoding: encoding, + coded: false }; properties.glyphs = this.extractEncoding(dict, xref, properties); + if (type.name == 'Type3') { + properties.coded = true; + var charProcs = xref.fetchIfRef(dict.get('CharProcs')); + var fontResources = xref.fetchIfRef(dict.get('Resources')) || resources; + properties.resources = fontResources; + for (var key in charProcs.map) { + var glyphStream = xref.fetchIfRef(charProcs.map[key]); + properties.glyphs[key].code = this.evaluate(glyphStream, + xref, + fontResources); + } + } + return { name: fontName.name, dict: baseDict, @@ -5223,41 +5246,72 @@ var CanvasGraphics = (function canvasGraphics() { var ctx = this.ctx; var current = this.current; var font = current.font; - - ctx.save(); - ctx.transform.apply(ctx, current.textMatrix); - ctx.scale(1, -1); - ctx.translate(current.x, -1 * current.y); - ctx.transform.apply(ctx, font.textMatrix || IDENTITY_MATRIX); - var glyphs = font.charsToGlyphs(text); var fontSize = current.fontSize; var charSpacing = current.charSpacing; var wordSpacing = current.wordSpacing; var textHScale = current.textHScale; - ctx.scale(1 / textHScale, 1); - - var width = 0; var glyphsLength = glyphs.length; - for (var i = 0; i < glyphsLength; ++i) { - var glyph = glyphs[i]; - if (glyph === null) { - // word break - width += wordSpacing; - continue; + if (font.coded) { + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.translate(current.x, current.y); + + var fontMatrix = font.fontMatrix || IDENTITY_MATRIX; + ctx.scale(1 / textHScale, 1); + for (var i = 0; i < glyphsLength; ++i) { + + var glyph = glyphs[i]; + if (glyph === null) { + // word break + this.ctx.translate(wordSpacing, 0); + continue; + } + + this.save(); + ctx.scale(fontSize, fontSize); + ctx.transform.apply(ctx, fontMatrix); + this.execute(glyph.code, this.xref, font.resources); + this.restore(); + + var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); + var width = transformed[0] * fontSize + charSpacing; + + ctx.translate(width, 0); + current.x += width; + } + ctx.restore(); + } else { + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.scale(1, -1); + ctx.translate(current.x, -1 * current.y); + ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX); - var unicode = glyph.unicode; - var char = (unicode >= 0x10000) ? - String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10), - 0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode); + ctx.scale(1 / textHScale, 1); - ctx.fillText(char, width, 0); - width += glyph.width * fontSize * 0.001 + charSpacing; + var width = 0; + for (var i = 0; i < glyphsLength; ++i) { + var glyph = glyphs[i]; + if (glyph === null) { + // word break + width += wordSpacing; + continue; + } + + var unicode = glyph.unicode; + var char = (unicode >= 0x10000) ? + String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10), + 0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode); + + ctx.fillText(char, width, 0); + width += glyph.width * fontSize * 0.001 + charSpacing; + } + current.x += width; + + ctx.restore(); } - current.x += width; - - this.ctx.restore(); }, showSpacedText: function canvasGraphicsShowSpacedText(arr) { var ctx = this.ctx; @@ -5295,8 +5349,8 @@ var CanvasGraphics = (function canvasGraphics() { // Type3 fonts setCharWidth: function canvasGraphicsSetCharWidth(xWidth, yWidth) { - TODO('type 3 fonts ("d0" operator) xWidth: ' + xWidth + ' yWidth: ' + - yWidth); + // We can safely ignore this since the width should be the same + // as the width in the Widths array. }, setCharWidthAndBounds: function canvasGraphicsSetCharWidthAndBounds(xWidth, yWidth, @@ -5304,9 +5358,11 @@ var CanvasGraphics = (function canvasGraphics() { lly, urx, ury) { - TODO('type 3 fonts ("d1" operator) xWidth: ' + xWidth + ' yWidth: ' + - yWidth + ' llx: ' + llx + ' lly: ' + lly + ' urx: ' + urx + - ' ury ' + ury); + // TODO? According the spec we're also suppose to ignore any operators + // that set color or include images while processing this type3 font. + this.rectangle(llx, lly, urx - llx, ury - lly); + this.clip(); + this.endPath(); }, // Color @@ -6413,6 +6469,7 @@ var PDFImage = (function pdfImage() { applyStencilMask: function applyStencilMask(buffer, inverseDecode) { var width = this.width, height = this.height; var bitStrideLength = (width + 7) >> 3; + this.image.reset(); var imgArray = this.image.getBytes(bitStrideLength * height); var imgArrayPos = 0; var i, j, mask, buf; @@ -6441,6 +6498,7 @@ var PDFImage = (function pdfImage() { // rows start at byte boundary; var rowBytes = (width * numComps * bpc + 7) >> 3; + this.image.reset(); var imgArray = this.image.getBytes(height * rowBytes); var comps = this.colorSpace.getRgbBuffer( @@ -6468,6 +6526,7 @@ var PDFImage = (function pdfImage() { // rows start at byte boundary; var rowBytes = (width * numComps * bpc + 7) >> 3; + this.image.reset(); var imgArray = this.image.getBytes(height * rowBytes); var comps = this.getComponents(imgArray); diff --git a/test/pdfs/simpletype3font.pdf b/test/pdfs/simpletype3font.pdf new file mode 100644 index 000000000..f6fc5714c --- /dev/null +++ b/test/pdfs/simpletype3font.pdf @@ -0,0 +1,141 @@ +%PDF-1.4 +%öäüß +1 0 obj +<< +/Type /Catalog +/Version /1.4 +/Pages 2 0 R +>> +endobj +2 0 obj +<< +/Type /Pages +/Kids [3 0 R] +/Count 1 +>> +endobj +3 0 obj +<< +/Type /Page +/MediaBox [0 0 612 792] +/Parent 2 0 R +/Resources 4 0 R +/Contents 5 0 R +>> +endobj +4 0 obj +<< +/Font 6 0 R +/XObject << +>> +>> +endobj +5 0 obj +<< +/Length 7 0 R +>> +stream +/F0 12 Tf +BT +100 700 Td +(ababab) Tj +ET + +endstream +endobj +6 0 obj +<< +/F0 8 0 R +>> +endobj +7 0 obj +39 +endobj +8 0 obj +<< +/Type /Font +/Subtype /Type3 +/FontBBox [0 0 750 750] +/FontMatrix [0.001 0 0 0.001 0 0] +/CharProcs 9 0 R +/Encoding 10 0 R +/FirstChar 97 +/LastChar 98 +/Widths [1000 1000] +/FontDescriptor 11 0 R +>> +endobj +9 0 obj +<< +/square 12 0 R +/triangle 13 0 R +>> +endobj +10 0 obj +<< +/Type /Encoding +/Differences [97 /square /triangle] +>> +endobj +11 0 obj +<< +/Type /FontDescriptor +/FontName /SandT +/Flags 262178 +/Ascent 0 +/CapHeight 0 +/Descent 0 +/ItalicAngle 0 +/StemV 0 +/FontBBox [0 0 750 750] +>> +endobj +12 0 obj +<< +/Length 14 0 R +>> +stream +1000 0 0 0 750 750 d1 0 0 750 750 re f +endstream +endobj +13 0 obj +<< +/Length 15 0 R +>> +stream +1000 0 0 0 750 750 d1 0 0 m 375 750 l 750 0 l f +endstream +endobj +14 0 obj +38 +endobj +15 0 obj +47 +endobj +xref +0 16 +0000000000 65535 f +0000000015 00000 n +0000000078 00000 n +0000000135 00000 n +0000000239 00000 n +0000000287 00000 n +0000000381 00000 n +0000000412 00000 n +0000000430 00000 n +0000000641 00000 n +0000000694 00000 n +0000000768 00000 n +0000000925 00000 n +0000001020 00000 n +0000001124 00000 n +0000001143 00000 n +trailer +<< +/Root 1 0 R +/ID [ ] +/Size 16 +>> +startxref +1162 +%%EOF diff --git a/test/test_manifest.json b/test/test_manifest.json index 71738f192..1d3904ca1 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -164,5 +164,11 @@ "rounds": 1, "skipPages": [ 16 ], "type": "load" + }, + { "id": "simpletype3font", + "file": "pdfs/simpletype3font.pdf", + "link": false, + "rounds": 1, + "type": "load" } ]