diff --git a/src/bidi.js b/src/bidi.js index 568d226f2..2d7fd9407 100644 --- a/src/bidi.js +++ b/src/bidi.js @@ -139,16 +139,16 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } } - function BidiResult(str, isLTR) { + function BidiResult(str, isLTR, vertical) { this.str = str; - this.ltr = isLTR; + this.dir = vertical ? 'ttb' : isLTR ? 'ltr' : 'rtl'; } - function bidi(str, startLevel) { + function bidi(str, startLevel, vertical) { var isLTR = true; var strLength = str.length; - if (strLength === 0) - return new BidiResult(str, isLTR); + if (strLength === 0 || vertical) + return new BidiResult(str, isLTR, vertical); // get types, fill arrays diff --git a/src/canvas.js b/src/canvas.js index 6168372d2..0765049a9 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -896,6 +896,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var textSelection = textLayer && !skipTextSelection ? true : false; var textRenderingMode = current.textRenderingMode; var canvasWidth = 0.0; + var vertical = font.vertical; + var defaultVMetrics = font.defaultVMetrics; // Type3 fonts - each glyph is a "mini-PDF" if (font.coded) { @@ -969,25 +971,37 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } var character = glyph.fontChar; - var charWidth = glyph.width * fontSize * current.fontMatrix[0] + + var vmetric = glyph.vmetric || defaultVMetrics; + if (vertical) { + var vx = vmetric[1] * fontSize * current.fontMatrix[0]; + var vy = vmetric[2] * fontSize * current.fontMatrix[0]; + } + var width = vmetric ? -vmetric[0] : glyph.width; + var charWidth = width * fontSize * current.fontMatrix[0] + charSpacing * current.fontDirection; if (!glyph.disabled) { - var scaledX = x / fontSizeScale; + if (vertical) { + var scaledX = vx / fontSizeScale; + var scaledY = (x + vy) / fontSizeScale; + } else { + var scaledX = x / fontSizeScale; + var scaledY = 0; + } switch (textRenderingMode) { default: // other unsupported rendering modes case TextRenderingMode.FILL: case TextRenderingMode.FILL_ADD_TO_PATH: - ctx.fillText(character, scaledX, 0); + ctx.fillText(character, scaledX, scaledY); break; case TextRenderingMode.STROKE: case TextRenderingMode.STROKE_ADD_TO_PATH: - ctx.strokeText(character, scaledX, 0); + ctx.strokeText(character, scaledX, scaledY); break; case TextRenderingMode.FILL_STROKE: case TextRenderingMode.FILL_STROKE_ADD_TO_PATH: - ctx.fillText(character, scaledX, 0); - ctx.strokeText(character, scaledX, 0); + ctx.fillText(character, scaledX, scaledY); + ctx.strokeText(character, scaledX, scaledY); break; case TextRenderingMode.INVISIBLE: case TextRenderingMode.ADD_TO_PATH: @@ -995,7 +1009,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } if (textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) { var clipCtx = this.getCurrentTextClipping(); - clipCtx.fillText(character, scaledX, 0); + clipCtx.fillText(character, scaledX, scaledY); } } @@ -1003,12 +1017,23 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { canvasWidth += charWidth; } - current.x += x * textHScale; + if (vertical) { + current.y -= x * textHScale; + } else { + current.x += x * textHScale; + } ctx.restore(); } if (textSelection) { geom.canvasWidth = canvasWidth; + if (vertical) { + var vmetric = font.defaultVMetrics; + geom.x -= vmetric[1] * fontSize * current.fontMatrix[0] / + fontSizeScale * geom.hScale; + geom.y += vmetric[2] * fontSize * current.fontMatrix[0] / + fontSizeScale * geom.vScale; + } this.textLayer.appendText(geom); } @@ -1027,6 +1052,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var geom; var canvasWidth = 0.0; var textSelection = textLayer ? true : false; + var vertical = font.vertical; if (textSelection) { ctx.save(); @@ -1039,7 +1065,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var e = arr[i]; if (isNum(e)) { var spacingLength = -e * fontSize * textHScale; - current.x += spacingLength; + if (vertical) { + current.y += spacingLength; + } else { + current.x += spacingLength; + } if (textSelection) canvasWidth += spacingLength; @@ -1055,6 +1085,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { if (textSelection) { geom.canvasWidth = canvasWidth; + if (vertical) { + var fontSizeScale = current.fontSizeScale; + var vmetric = font.defaultVMetrics; + geom.x -= vmetric[1] * fontSize * current.fontMatrix[0] / + fontSizeScale * geom.hScale; + geom.y += vmetric[2] * fontSize * current.fontMatrix[0] / + fontSizeScale * geom.vScale; + } this.textLayer.appendText(geom); } }, diff --git a/src/evaluator.js b/src/evaluator.js index 2b98de81a..28f5d8430 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -796,7 +796,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } // switch if (chunk !== '') { - bidiTexts.push(PDFJS.bidi(chunk, -1)); + var bidiText = PDFJS.bidi(chunk, -1, font.vertical); + bidiTexts.push(bidiText); chunk = ''; } @@ -831,10 +832,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }; } - var cidEncoding = baseDict.get('Encoding'); - if (isName(cidEncoding)) - properties.cidEncoding = cidEncoding.name; - var cidToGidMap = dict.get('CIDToGIDMap'); if (isStream(cidToGidMap)) properties.cidToGidMap = this.readCidToGidMap(cidToGidMap); @@ -1031,6 +1028,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { properties) { var glyphsWidths = []; var defaultWidth = 0; + var glyphsVMetrics = []; + var defaultVMetrics; if (properties.composite) { defaultWidth = dict.get('DW') || 1000; @@ -1049,6 +1048,26 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } } } + + if (properties.vertical) { + var vmetrics = dict.get('DW2') || [880, -1000]; + defaultVMetrics = [vmetrics[1], vmetrics[1] / 2, vmetrics[0]]; + vmetrics = dict.get('W2'); + if (vmetrics) { + for (var i = 0, ii = vmetrics.length; i < ii; i++) { + var start = vmetrics[i++]; + var code = xref.fetchIfRef(vmetrics[i]); + if (isArray(code)) { + for (var j = 0, jj = code.length; j < jj; j++) + glyphsVMetrics[start++] = [code[j++], code[j++], code[j]]; + } else { + var vmetric = [vmetrics[++i], vmetrics[++i], vmetrics[++i]]; + for (var j = start; j <= code; j++) + glyphsVMetrics[j] = vmetric; + } + } + } + } } else { var firstChar = properties.firstChar; var widths = dict.get('Widths'); @@ -1089,6 +1108,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { properties.defaultWidth = defaultWidth; properties.widths = glyphsWidths; + properties.defaultVMetrics = defaultVMetrics; + properties.vmetrics = glyphsVMetrics; }, isSerifFont: function PartialEvaluator_isSerifFont(baseFontName) { @@ -1260,6 +1281,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { italicAngle: descriptor.get('ItalicAngle'), coded: false }; + + if (composite) { + var cidEncoding = baseDict.get('Encoding'); + if (isName(cidEncoding)) { + properties.cidEncoding = cidEncoding.name; + properties.vertical = /-V$/.test(cidEncoding.name); + } + } this.extractWidths(dict, xref, descriptor, properties); this.extractDataStructures(dict, baseDict, xref, properties); diff --git a/src/fonts.js b/src/fonts.js index cf8241f11..0e7424d17 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -2363,6 +2363,11 @@ var Font = (function FontClosure() { // Trying to fix encoding using glyph CIDSystemInfo. this.loadCidToUnicode(properties); this.cidEncoding = properties.cidEncoding; + this.vertical = properties.vertical; + if (this.vertical) { + this.vmetrics = properties.vmetrics; + this.defaultVMetrics = properties.defaultVMetrics; + } if (properties.toUnicode) this.toUnicode = properties.toUnicode; @@ -4449,17 +4454,15 @@ var Font = (function FontClosure() { var fontCharCode, width, operatorList, disabled; var width = this.widths[charcode]; + var vmetric = this.vmetrics && this.vmetrics[charcode]; switch (this.type) { case 'CIDFontType0': - if (this.noUnicodeAdaptation) { - width = this.widths[this.unicodeToCID[charcode] || charcode]; - } - fontCharCode = this.toFontChar[charcode] || charcode; - break; case 'CIDFontType2': + var cid = this.unicodeToCID[charcode] || charcode; if (this.noUnicodeAdaptation) { - width = this.widths[this.unicodeToCID[charcode] || charcode]; + width = this.widths[cid]; + vmetric = this.vmetrics && this.vmetrics[cid]; } fontCharCode = this.toFontChar[charcode] || charcode; break; @@ -4523,6 +4526,7 @@ var Font = (function FontClosure() { fontChar: String.fromCharCode(fontCharCode), unicode: unicodeChars, width: width, + vmetric: vmetric, disabled: disabled, operatorList: operatorList }; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 9e0c0ffd5..29a8e3880 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -44,4 +44,5 @@ !noembed-jis7.pdf !noembed-eucjp.pdf !noembed-sjis.pdf +!vertical.pdf !issue2099-1.pdf diff --git a/test/pdfs/vertical.pdf b/test/pdfs/vertical.pdf new file mode 100644 index 000000000..22245220a Binary files /dev/null and b/test/pdfs/vertical.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 2ac724ad8..a455b116c 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -923,6 +923,12 @@ "rounds": 1, "type": "eq" }, + { "id": "vertical", + "file": "pdfs/vertical.pdf", + "md5": "8a74d33504701edcefeef2afd022765e", + "rounds": 1, + "type": "eq" + }, { "id": "issue2099-1", "file": "pdfs/issue2099-1.pdf", "md5": "c7eca682d70a976dfc4b7e64d3e9f1ce", diff --git a/web/viewer.css b/web/viewer.css index 602477ba8..5b5a93dfd 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -1186,7 +1186,7 @@ canvas { .textLayer > div { color: transparent; position: absolute; - line-height:1.3; + line-height:1; white-space:pre; } diff --git a/web/viewer.js b/web/viewer.js index d19f3005a..905e95f12 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -2535,6 +2535,7 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) { this.renderLayer = function textLayerBuilderRenderLayer() { var self = this; var textDivs = this.textDivs; + var bidiTexts = this.textContent.bidiTexts; var textLayerDiv = this.textLayerDiv; var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); @@ -2555,8 +2556,11 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) { if (width > 0) { var textScale = textDiv.dataset.canvasWidth / width; - CustomStyle.setProp('transform' , textDiv, - 'scale(' + textScale + ', 1)'); + var transform = 'scale(' + textScale + ', 1)'; + if (bidiTexts[i].dir === 'ttb') { + transform = 'rotate(90deg) ' + transform; + } + CustomStyle.setProp('transform' , textDiv, transform); CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%'); textLayerDiv.appendChild(textDiv); @@ -2621,7 +2625,8 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) { var textDiv = textDivs[i]; textDiv.textContent = bidiText.str; - textDiv.dir = bidiText.ltr ? 'ltr' : 'rtl'; + // bidiText.dir may be 'ttb' for vertical texts. + textDiv.dir = bidiText.dir === 'rtl' ? 'rtl' : 'ltr'; } this.setupRenderLayoutTimer();