Ответ 1
Как сказано в разделе Respwned, нет простого ответа, который будет работать во всех случаях. При этом здесь есть два подхода, которые, похоже, работают достаточно хорошо. Оба имеют верхние и нижние стороны.
Подход 1
Внутри метод getTextContent
использует то, что называется EvaluatorPreprocessor
для анализа операторов PDF, и поддерживает графическое состояние. Так что мы можем сделать, реализовать пользовательский EvaluatorPreprocessor
, перезаписать метод preprocessCommand
и использовать его, чтобы добавить текущий цвет текста к графическому состоянию. Как только это будет установлено, в любое время создается новый текстовый фрагмент, мы можем добавить атрибут цвета и установить его в текущее состояние цвета.
Недостатками этого подхода являются:
-
Требуется изменить исходный код PDFJS. Это также сильно зависит от текущая реализация PDFJS, и может сломаться, если это изменилось.
-
Это не удастся в случаях, когда текст используется как путь к заполнению изображения. В некоторых создателях 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;
})();
Затем вам нужно изменить метод getTextContent для использования нового оценщика:
var preprocessor = new CustomEvaluatorPreprocessor(stream, xref, stateManager, resources);
И наконец, в newTextChunk добавьте атрибут цвета:
color: stateManager.state.fillColor
Подход 2
Другой подход заключается в извлечении текстовых ограничивающих блоков через getTextContent
, рендеринг страницы и для каждого текста, получение значений пикселей, которые находятся в пределах его границ, и принять это за цвет заливки.
Недостатками этого подхода являются:
- Выделенные текстовые ограничивающие поля не всегда корректны, а в некоторых случаях могут даже полностью отключиться (например, повернутый текст). Если ограничивающая рамка не охватывает хотя бы частично фактический текст на холсте, то этот метод не удастся. Мы можем восстановиться после полных сбоев, проверив, что текстовые пиксели имеют дисперсию цвета больше порога. Обоснование заключается в том, что если ограничивающая рамка полностью соответствует фону, она будет иметь небольшую дисперсию, и в этом случае мы можем отказаться от цвета текста по умолчанию (или, возможно, даже цвета 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];
}
};