pdf.js:获取文本颜色

div*_*ges 13 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 ) );
}
Run Code Online (Sandbox Code Playgroud)

我得到一个像这样的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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

lev*_*evi 8

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

方法1

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

这种方法的缺点是:

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

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

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

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

首先,您必须在EvaluatorPreprocessor定义之后定义自定义求值程序.

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;
})();
Run Code Online (Sandbox Code Playgroud)

接下来,您需要修改getTextContent方法以使用新的求值程序:

var preprocessor = new CustomEvaluatorPreprocessor(stream, xref, stateManager, resources);
Run Code Online (Sandbox Code Playgroud)

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

color: stateManager.state.fillColor
Run Code Online (Sandbox Code Playgroud)

方法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];
    }
};
Run Code Online (Sandbox Code Playgroud)


Fiz*_*izz 6

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

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

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

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

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

希望这可以帮助.