Modifiy the way to compute baseline to have a better match between canvas and text layer

- use ascent of the fallback font instead of the one from pdf to position spans
 - use TextMetrics.fontBoundingBoxAscent if available or
 - use a basic heuristic to guess ascent in drawing char on a canvas
 - compute ascent as a ratio of font height
This commit is contained in:
Calixte Denizet 2021-01-23 18:21:48 +01:00
parent d5cad9ad3f
commit b4421b076a

View File

@ -54,6 +54,9 @@ import {
*/
const renderTextLayer = (function renderTextLayerClosure() {
const MAX_TEXT_DIVS_TO_RENDER = 100000;
const DEFAULT_FONT_SIZE = 30;
const DEFAULT_FONT_ASCENT = 0.8;
const ascentCache = new Map();
const NonWhitespaceRegexp = /\S/;
@ -61,7 +64,70 @@ const renderTextLayer = (function renderTextLayerClosure() {
return !NonWhitespaceRegexp.test(str);
}
function appendText(task, geom, styles) {
function getAscent(fontFamily, ctx) {
const cachedAscent = ascentCache.get(fontFamily);
if (cachedAscent) {
return cachedAscent;
}
ctx.save();
ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`;
const metrics = ctx.measureText("");
// Both properties aren't available by default in Firefox.
let ascent = metrics.fontBoundingBoxAscent;
let descent = Math.abs(metrics.fontBoundingBoxDescent);
if (ascent) {
ctx.restore();
const ratio = ascent / (ascent + descent);
ascentCache.set(fontFamily, ratio);
return ratio;
}
// Try basic heuristic to guess ascent/descent.
// Draw a g with baseline at 0,0 and then get the line
// number where a pixel has non-null red component (starting
// from bottom).
ctx.strokeStyle = "red";
ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
ctx.strokeText("g", 0, 0);
let pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE)
.data;
descent = 0;
for (let i = pixels.length - 1 - 3; i >= 0; i -= 4) {
if (pixels[i] > 0) {
descent = Math.ceil(i / 4 / DEFAULT_FONT_SIZE);
break;
}
}
// Draw an A with baseline at 0,DEFAULT_FONT_SIZE and then get the line
// number where a pixel has non-null red component (starting
// from top).
ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
ctx.strokeText("A", 0, DEFAULT_FONT_SIZE);
pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
ascent = 0;
for (let i = 0, ii = pixels.length; i < ii; i += 4) {
if (pixels[i] > 0) {
ascent = DEFAULT_FONT_SIZE - Math.floor(i / 4 / DEFAULT_FONT_SIZE);
break;
}
}
ctx.restore();
if (ascent) {
const ratio = ascent / (ascent + descent);
ascentCache.set(fontFamily, ratio);
return ratio;
}
ascentCache.set(fontFamily, DEFAULT_FONT_ASCENT);
return DEFAULT_FONT_ASCENT;
}
function appendText(task, geom, styles, ctx) {
// Initialize all used properties to keep the caches monomorphic.
const textDiv = document.createElement("span");
const textDivProperties = {
@ -90,12 +156,7 @@ const renderTextLayer = (function renderTextLayerClosure() {
angle += Math.PI / 2;
}
const fontHeight = Math.hypot(tx[2], tx[3]);
let fontAscent = fontHeight;
if (style.ascent) {
fontAscent = style.ascent * fontAscent;
} else if (style.descent) {
fontAscent = (1 + style.descent) * fontAscent;
}
const fontAscent = fontHeight * getAscent(style.fontFamily, ctx);
let left, top;
if (angle === 0) {
@ -578,7 +639,7 @@ const renderTextLayer = (function renderTextLayerClosure() {
_processItems(items, styleCache) {
for (let i = 0, len = items.length; i < len; i++) {
this._textContentItemsStr.push(items[i].str);
appendText(this, items[i], styleCache);
appendText(this, items[i], styleCache, this._layoutTextCtx);
}
},
@ -628,6 +689,8 @@ const renderTextLayer = (function renderTextLayerClosure() {
// The temporary canvas is used to measure text length in the DOM.
const canvas = this._document.createElement("canvas");
canvas.height = canvas.width = DEFAULT_FONT_SIZE;
if (
typeof PDFJSDev === "undefined" ||
PDFJSDev.test("MOZCENTRAL || GENERIC")