我有一个简单的 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
}
}
}
但是,我一直没能找到一种方法来确定每个单词的颜色。当我渲染它时,它会正确渲染,所以我知道信息就在某处。我可以在什么地方访问它吗?
最佳答案
正如 Respawned 所暗示的,没有适用于所有情况的简单答案。话虽这么说,这里有两种方法似乎效果很好。两者都有优点和缺点。
方法一
在内部,getTextContent
方法使用所谓的 EvaluatorPreprocessor
来解析 PDF 运算符,并保持图形状态。所以我们可以做的是,实现一个自定义的EvaluatorPreprocessor
,覆盖preprocessCommand
方法,并使用它来将当前文本颜色添加到图形状态。一旦就绪,无论何时创建新文本 block ,我们都可以添加颜色属性,并将其设置为当前颜色状态。
这种方法的缺点是:
需要修改 PDFJS 源代码。也很大程度上取决于 当前 PDFJS 的实现,如果这是 改变了。
在将文本用作要填充图像的路径的情况下,它将失败。在某些 PDF 创建器(如 Photoshop)中,它创建彩色文本的方式是,它首先从所有给定的文本字符创建一个剪切路径,然后在该路径上绘制一个纯色图像。因此,推断填充颜色的唯一方法是从图像中读取像素值,这需要将其绘制到 Canvas 上。即使连接到
paintChar
在这里也没有太大帮助,因为填充颜色只会在稍后出现。
好处是,它相当健壮,并且无论页面背景如何都能正常工作。它也不需要将任何东西渲染到 Canvas 上,因此它可以完全在后台线程中完成。
代码
所有修改都在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
提取文本边界框,呈现页面,对于每个文本,获取位于其边界内的像素值,并将其作为填充颜色.
这种方法的缺点是:
- 计算出的文本边界框并不总是正确的,在某些情况下甚至可能完全不正确(例如:旋转的文本)。如果边界框未至少部分覆盖 Canvas 上的实际文本,则此方法将失败。我们可以从完全失败中恢复,方法是检查文本像素的颜色方差是否大于阈值。理由是,如果边界框完全是背景,它几乎没有变化,在这种情况下我们可以回退到默认文本颜色(或者甚至可能是 k 最近邻的颜色)。
- 该方法假定文本比背景暗。否则,背景可能会被误认为填充颜色。在大多数情况下这不会成为问题,因为大多数文档都有白色背景。
好处是,它很简单,不需要弄乱 PDFJS 源代码。此外,它也适用于文本用作剪切路径并填充图像的情况。虽然当您有复杂的图像填充时这可能会变得模糊,在这种情况下,文本颜色的选择会变得模糊。
演示
要测试的 PDF 示例:
- https://www.dropbox.com/s/0t5vtu6qqsdm1d4/color-test.pdf?dl=1
- https://www.dropbox.com/s/cq0067u80o79o7x/testTextColour.pdf?dl=1
代码
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];
}
};
关于javascript - pdf.js:获取文本颜色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27713660/