Merge pull request #12896 from calixteman/text_layer
Modifiy the way to compute baseline to have a better match between canvas and text layer
This commit is contained in:
commit
c79fd71457
@ -54,6 +54,9 @@ import {
|
|||||||
*/
|
*/
|
||||||
const renderTextLayer = (function renderTextLayerClosure() {
|
const renderTextLayer = (function renderTextLayerClosure() {
|
||||||
const MAX_TEXT_DIVS_TO_RENDER = 100000;
|
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/;
|
const NonWhitespaceRegexp = /\S/;
|
||||||
|
|
||||||
@ -61,7 +64,70 @@ const renderTextLayer = (function renderTextLayerClosure() {
|
|||||||
return !NonWhitespaceRegexp.test(str);
|
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.
|
// Initialize all used properties to keep the caches monomorphic.
|
||||||
const textDiv = document.createElement("span");
|
const textDiv = document.createElement("span");
|
||||||
const textDivProperties = {
|
const textDivProperties = {
|
||||||
@ -90,12 +156,7 @@ const renderTextLayer = (function renderTextLayerClosure() {
|
|||||||
angle += Math.PI / 2;
|
angle += Math.PI / 2;
|
||||||
}
|
}
|
||||||
const fontHeight = Math.hypot(tx[2], tx[3]);
|
const fontHeight = Math.hypot(tx[2], tx[3]);
|
||||||
let fontAscent = fontHeight;
|
const fontAscent = fontHeight * getAscent(style.fontFamily, ctx);
|
||||||
if (style.ascent) {
|
|
||||||
fontAscent = style.ascent * fontAscent;
|
|
||||||
} else if (style.descent) {
|
|
||||||
fontAscent = (1 + style.descent) * fontAscent;
|
|
||||||
}
|
|
||||||
|
|
||||||
let left, top;
|
let left, top;
|
||||||
if (angle === 0) {
|
if (angle === 0) {
|
||||||
@ -578,7 +639,7 @@ const renderTextLayer = (function renderTextLayerClosure() {
|
|||||||
_processItems(items, styleCache) {
|
_processItems(items, styleCache) {
|
||||||
for (let i = 0, len = items.length; i < len; i++) {
|
for (let i = 0, len = items.length; i < len; i++) {
|
||||||
this._textContentItemsStr.push(items[i].str);
|
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.
|
// The temporary canvas is used to measure text length in the DOM.
|
||||||
const canvas = this._document.createElement("canvas");
|
const canvas = this._document.createElement("canvas");
|
||||||
|
canvas.height = canvas.width = DEFAULT_FONT_SIZE;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof PDFJSDev === "undefined" ||
|
typeof PDFJSDev === "undefined" ||
|
||||||
PDFJSDev.test("MOZCENTRAL || GENERIC")
|
PDFJSDev.test("MOZCENTRAL || GENERIC")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user