pdf.js:获取文本颜色

时间:2014-12-30 22:45:25

标签: javascript pdf.js

我有一个简单的pdf文件,其中包含“Hello world”字样,每个文字都有不同的颜色。

我正在加载PDF,如下所示:

PDFJS.getDocument('test.pdf').then( onPDF );

function onPDF( pdf )
{
    pdf.getPage( 1 ).then( onPage );
}

function onPage( page )
{
    page.getTextContent().then( onText );
}

function onText( text )
{   
    console.log( JSON.stringify( text ) );
}

我得到一个像这样的JSON输出:

{
    "items" : [{
            "str" : "Hello ",
            "dir" : "ltr",
            "width" : 29.592,
            "height" : 12,
            "transform" : [12, 0, 0, 12, 56.8, 774.1],
            "fontName" : "g_font_1"
        }, {
            "str" : "world",
            "dir" : "ltr",
            "width" : 27.983999999999998,
            "height" : 12,
            "transform" : [12, 0, 0, 12, 86.5, 774.1],
            "fontName" : "g_font_1"
        }
    ],
    "styles" : {
        "g_font_1" : {
            "fontFamily" : "serif",
            "ascent" : 0.891,
            "descent" : 0.216
        }
    }
}

但是,我无法找到确定每个单词颜色的方法。当我渲染它时,它渲染得很好,所以我知道信息就在那里。有什么地方我可以访问这个吗?

3 个答案:

答案 0 :(得分:8)

正如重生所暗示的那样,没有简单的答案可以适用于所有情况。话虽如此,这里有两种似乎运作良好的方法。两者都有好处和缺点。

方法1

在内部,getTextContent方法使用称为EvaluatorPreprocessor的方法来解析PDF运算符,并维护图形状态。所以我们可以做的是,实现自定义EvaluatorPreprocessor,覆盖preprocessCommand方法,并使用它将当前文本颜色添加到图形状态。一旦到位,无论何时创建新的文本块,我们都可以添加颜色属性,并将其设置为当前颜色状态。

这种方法的缺点是:

  1. 需要修改PDFJS源代码。它也在很大程度上取决于 PDFJS的当前实现,如果是这样可能会破坏 改变。

  2. 如果将文本用作要填充图像的路径,则会失败。在某些PDF创建者(如Photoshop)中,它创建彩色文本的方式是,它首先从所有给定的文本字符创建剪切路径,然后在路径上绘制实心图像。因此,推断填充颜色的唯一方法是从图像中读取像素值,这需要将其绘制到画布上。即使挂钩paintChar也不会有太多帮助,因为填充颜色只会在以后出现。

  3. 优点是,它相当强大,无论页面背景如何都能正常工作。它也不需要渲染任何东西到画布,所以它可以完全在后台线程中完成。

    <强>代码

    所有修改均在core/evaluator.js文件中进行。

    首先,您必须在EvaluatorPreprocessor definition

    之后定义自定义评估程序
    var CustomEvaluatorPreprocessor = (function() {
        function CustomEvaluatorPreprocessor(stream, xref, stateManager, resources) {
            EvaluatorPreprocessor.call(this, stream, xref, stateManager);
            this.resources = resources;
            this.xref = xref;
    
            // set initial color state
            var state = this.stateManager.state;
            state.textRenderingMode = TextRenderingMode.FILL;
            state.fillColorSpace = ColorSpace.singletons.gray;
            state.fillColor = [0,0,0];
        }
    
        CustomEvaluatorPreprocessor.prototype = Object.create(EvaluatorPreprocessor.prototype);
    
        CustomEvaluatorPreprocessor.prototype.preprocessCommand = function(fn, args) {
            EvaluatorPreprocessor.prototype.preprocessCommand.call(this, fn, args);
            var state = this.stateManager.state;
            switch(fn) {
                case OPS.setFillColorSpace:
                    state.fillColorSpace = ColorSpace.parse(args[0], this.xref, this.resources);
                break;
                case OPS.setFillColor:
                     var cs = state.fillColorSpace;
                     state.fillColor = cs.getRgb(args, 0);
                break;
                case OPS.setFillGray:
                  state.fillColorSpace = ColorSpace.singletons.gray;
                  state.fillColor = ColorSpace.singletons.gray.getRgb(args, 0);
                break;
                case OPS.setFillCMYKColor:
                  state.fillColorSpace = ColorSpace.singletons.cmyk;
                  state.fillColor = ColorSpace.singletons.cmyk.getRgb(args, 0);
                break;
                case OPS.setFillRGBColor:
                    state.fillColorSpace = ColorSpace.singletons.rgb;
                    state.fillColor = ColorSpace.singletons.rgb.getRgb(args, 0);
                break;
            }
        };
    
        return CustomEvaluatorPreprocessor;
    })();
    

    接下来,您需要修改getTextContent method以使用新的评估者:

    var preprocessor = new CustomEvaluatorPreprocessor(stream, xref, stateManager, resources);
    

    最后,在newTextChunk方法中,添加一个颜色属性:

    color: stateManager.state.fillColor
    

    方法2

    另一种方法是通过getTextContent提取文本边界框,渲染页面,并为每个文本获取位于其边界内的像素值,并将其作为填充颜色。

    这种方法的缺点是:

    1. 计算出的文本边界框并不总是正确的,在某些情况下甚至可能完全关闭(例如:旋转文本)。如果边界框至少部分不覆盖画布上的实际文本,则此方法将失败。通过检查文本像素的颜色方差是否大于阈值,我们可以从完全失败中恢复。理由是,如果边界框完全是背景,它将没有多大的变化,在这种情况下,我们可以回退到默认文本颜色(或者甚至可能是k个最近邻居的颜色)。
    2. 该方法假定文本比背景暗。否则,背景可能被误认为填充颜色。在大多数情况下,这不是问题,因为大多数文档都有白色背景。
    3. 优点是,它很简单,并且不需要弄乱PDFJS源代码。此外,它还适用于将文本用作剪切路径并填充图像的情况。虽然当你有复杂的图像填充时,这会变得模糊,但在这种情况下,文本颜色的选择变得模糊不清。

      演示

      http://jsfiddle.net/x2rajt5g/

      要测试的PDF样本:

      <强>代码

      function parseColors(canvasImgData, texts) {
          var data = canvasImgData.data,
              width = canvasImgData.width,
              height = canvasImgData.height,
              defaultColor = [0, 0, 0],
              minVariance = 20;
      
          texts.forEach(function (t) {
              var left = Math.floor(t.transform[4]),
                  w = Math.round(t.width),
                  h = Math.round(t.height),
                  bottom = Math.round(height - t.transform[5]),
                  top = bottom - h,
                  start = (left + (top * width)) * 4,
                  color = [],
                  best = Infinity,
                  stat = new ImageStats();
      
              for (var i, v, row = 0; row < h; row++) {
                  i = start + (row * width * 4);
                  for (var col = 0; col < w; col++) {
                      if ((v = data[i] + data[i + 1] + data[i + 2]) < best) { // the darker the "better"
                          best = v;
                          color[0] = data[i];
                          color[1] = data[i + 1];
                          color[2] = data[i + 2];
                      }
                      stat.addPixel(data[i], data[i+1], data[i+2]);
                      i += 4;
                  }
              }
              var stdDev = stat.getStdDev();
              t.color = stdDev < minVariance ? defaultColor : color;
          });
      }
      
      function ImageStats() {
          this.pixelCount = 0;
          this.pixels = [];
          this.rgb = [];
          this.mean = 0;
          this.stdDev = 0;
      }
      
      ImageStats.prototype = {
          addPixel: function (r, g, b) {
              if (!this.rgb.length) {
                  this.rgb[0] = r;
                  this.rgb[1] = g;
                  this.rgb[2] = b;
              } else {
                  this.rgb[0] += r;
                  this.rgb[1] += g;
                  this.rgb[2] += b;
              }
              this.pixelCount++;
              this.pixels.push([r,g,b]);
          },
      
          getStdDev: function() {
              var mean = [
                  this.rgb[0] / this.pixelCount,
                  this.rgb[1] / this.pixelCount,
                  this.rgb[2] / this.pixelCount
              ];
              var diff = [0,0,0];
              this.pixels.forEach(function(p) {
                  diff[0] += Math.pow(mean[0] - p[0], 2);
                  diff[1] += Math.pow(mean[1] - p[1], 2);
                  diff[2] += Math.pow(mean[2] - p[2], 2);
              });
              diff[0] = Math.sqrt(diff[0] / this.pixelCount);
              diff[1] = Math.sqrt(diff[1] / this.pixelCount);
              diff[2] = Math.sqrt(diff[2] / this.pixelCount);
              return diff[0] + diff[1] + diff[2];
          }
      };
      

答案 1 :(得分:6)

如果你想完美地实现这个问题,那么这个问题实际上是非常困难的......或者如果你能使用只在某些时候工作的解决方案,这个问题就相对容易了。

首先,要意识到getTextContent旨在用于可搜索的文本提取,以及它的所有目的。

上面的评论中建议您使用page.getOperatorList(),但这基本上是在您的代码中重新实现整个PDF绘图模型......这基本上是愚蠢的,因为PDFJS中最大的一块就是这样......除了文本提取的目的,但是为了渲染到画布。所以你要做的就是破解canvas.js,这样它不仅可以设置内部旋钮,还可以对代码进行一些回调。唉,如果你这样做,你就无法使用股票PDFJS,我更加怀疑你的颜色提取目标将被视为对PDFJS非常有用&#39;主要目的,因此您的更改可能不会被上游接受,因此您可能需要维护自己的PDFJS分支。

在这个可怕的警告之后,您需要最低限度地改变的是PDFJS解析PDF颜色操作符并设置其自己的画布绘画颜色的功能。这发生在function setFillColorN的第1566行(canvas.js)附近。您还需要挂钩文本渲染...这是canvas.js级别的字符渲染器,即1270行左右的CanvasGraphics_paintChar。有了这两个钩子,您将获得一个流在字符绘制序列之间散布颜色变化的回调。因此,您可以从简单的颜色案例中轻松地重建字符序列的颜色。

现在我找到了一个非常丑陋的部分:PDF具有极其复杂的颜色模型。首先,有两种颜色可用于绘制任何内容,包括文本:填充颜色和笔触(轮廓)颜色。到目前为止还不太可怕,但颜色是ColorSpace中的一个索引...其中有几个,RGB只有一种可能性。然后还有alpha和合成模式,因此(各种alpha)的层可以根据合成模式产生不同的最终颜色。并且PDFJS没有一个地方可以从层中积累颜色......它只是在它们到来时将它们描绘出来。因此,如果您只提取填充颜色更改并忽略alpha,合成等,它将起作用,但不适用于复杂文档。

希望这有帮助。

答案 2 :(得分:1)

无需修补 pdfjs,t​​ransform 属性给出了 x 和 y,因此您可以浏览运算符列表并找到此时文本操作之前的 setFillColor 操作。