我有一个简单的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
}
}
}
但是,我无法找到确定每个单词颜色的方法。当我渲染它时,它渲染得很好,所以我知道信息就在那里。有什么地方我可以访问这个吗?
答案 0 :(得分:8)
正如重生所暗示的那样,没有简单的答案可以适用于所有情况。话虽如此,这里有两种似乎运作良好的方法。两者都有好处和缺点。
在内部,getTextContent
方法使用称为EvaluatorPreprocessor
的方法来解析PDF运算符,并维护图形状态。所以我们可以做的是,实现自定义EvaluatorPreprocessor
,覆盖preprocessCommand
方法,并使用它将当前文本颜色添加到图形状态。一旦到位,无论何时创建新的文本块,我们都可以添加颜色属性,并将其设置为当前颜色状态。
这种方法的缺点是:
需要修改PDFJS源代码。它也在很大程度上取决于 PDFJS的当前实现,如果是这样可能会破坏 改变。
如果将文本用作要填充图像的路径,则会失败。在某些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
另一种方法是通过getTextContent
提取文本边界框,渲染页面,并为每个文本获取位于其边界内的像素值,并将其作为填充颜色。
这种方法的缺点是:
优点是,它很简单,并且不需要弄乱PDFJS源代码。此外,它还适用于将文本用作剪切路径并填充图像的情况。虽然当你有复杂的图像填充时,这会变得模糊,但在这种情况下,文本颜色的选择变得模糊不清。
演示
要测试的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,transform 属性给出了 x 和 y,因此您可以浏览运算符列表并找到此时文本操作之前的 setFillColor 操作。