From c5b8ee6a916cba4da15dbcfe1ec8d500154b24c5 Mon Sep 17 00:00:00 2001 From: vyv03354 Date: Fri, 8 Feb 2013 21:29:22 +0900 Subject: [PATCH] Implements vertical writing --- src/bidi.js | 10 +++---- src/canvas.js | 56 +++++++++++++++++++++++++++++++++------- src/evaluator.js | 39 ++++++++++++++++++++++++---- src/fonts.js | 16 +++++++----- test/pdfs/.gitignore | 1 + test/pdfs/vertical.pdf | Bin 0 -> 6905 bytes test/test_manifest.json | 6 +++++ web/viewer.css | 2 +- web/viewer.js | 11 +++++--- 9 files changed, 112 insertions(+), 29 deletions(-) create mode 100644 test/pdfs/vertical.pdf 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 9f04c9930..09a04cb56 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 0000000000000000000000000000000000000000..22245220a2a9027523c17932ba93f9b064ee7db9 GIT binary patch literal 6905 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 MSi{7`#oJZmUlQ 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 f10ef1e96..258311535 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -2515,6 +2515,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'); @@ -2535,8 +2536,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); @@ -2601,7 +2605,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();