pdf.js:获取文本颜色

问题描述 投票:13回答:2

我有一个简单的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
        }
    }
}

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

javascript pdf.js
2个回答
8
投票

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

Approach 1

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

这种方法的缺点是:

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

好处是,它相当强大,无论页面背景如何都可以工作。它也不需要渲染任何画布,所以它可以完全在后台线程中完成。

所有修改都在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

Approach 2

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

这种方法的缺点是:

  1. 计算出的文本边界框并不总是正确的,在某些情况下甚至可能完全关闭(例如:旋转文本)。如果边界框至少部分不覆盖画布上的实际文本,则此方法将失败。通过检查文本像素的颜色方差是否大于阈值,我们可以从完全失败中恢复。基本原理是,如果边界框完全是背景,它将具有很小的变化,在这种情况下,我们可以回退到默认文本颜色(或者甚至可能是k个最近邻居的颜色)。
  2. 该方法假定文本比背景更暗。否则,背景可能被误认为填充颜色。在大多数情况下,这不是问题,因为大多数文档都有白色背景。

好处是,它很简单,并且不需要弄乱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];
    }
};

6
投票

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

首先,要意识到getTextContent用于可搜索的文本提取,这就是它的目的。

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

在这个可怕的警告之后,您需要最小化更改的是PDFJS解析PDF颜色运算符并设置其自己的画布绘制颜色的函数。这发生在function setFillColorN的1566号线(canvas.js)附近。你还需要挂钩文本渲染...这是canvas.js级别的字符渲染器,即1270行左右的CanvasGraphics_paintChar。通过这两个钩子,你将得到一个回调流,用于颜色变化穿插在角色之间绘图序列。因此,您可以从简单的颜色案例中轻松地重建字符序列的颜色。

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

希望这可以帮助。

© www.soinside.com 2019 - 2024. All rights reserved.