1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-26 10:08:06 +02:00

XFA - Fix font scale factors (bug 1720888)

- All the scale factors in for the substitution font were wrong because of different glyph positions between Liberation and the other ones:
    - regenerate all the factors
  - Text may have polish chars for example and in this case the glyph widths were wrong:
    - treat substitution font as a composite one
    - add a map glyphIndex to unicode for Liberation in order to generate width array for cid font
This commit is contained in:
Calixte Denizet 2021-07-28 18:30:22 +02:00
parent ac5c4b7fd0
commit 4a4591bd2c
17 changed files with 1614 additions and 1248 deletions

View file

@ -560,6 +560,11 @@ function isPrintOnly(node) {
);
}
function getCurrentPara(node) {
const stack = node[$getTemplateRoot]()[$extra].paraStack;
return stack.length ? stack[stack.length - 1] : null;
}
function setPara(node, nodeStyle, value) {
if (value.attributes.class && value.attributes.class.includes("xfaRich")) {
if (nodeStyle) {
@ -570,13 +575,15 @@ function setPara(node, nodeStyle, value) {
nodeStyle.width = "auto";
}
}
if (node.para) {
const para = getCurrentPara(node);
if (para) {
// By definition exData are external data so para
// has no effect on it.
const valueStyle = value.attributes.style;
valueStyle.display = "flex";
valueStyle.flexDirection = "column";
switch (node.para.vAlign) {
switch (para.vAlign) {
case "top":
valueStyle.justifyContent = "start";
break;
@ -588,7 +595,7 @@ function setPara(node, nodeStyle, value) {
break;
}
const paraStyle = node.para[$toStyle]();
const paraStyle = para[$toStyle]();
for (const [key, val] of Object.entries(paraStyle)) {
if (!(key in valueStyle)) {
valueStyle[key] = val;
@ -598,7 +605,7 @@ function setPara(node, nodeStyle, value) {
}
}
function setFontFamily(xfaFont, fontFinder, style) {
function setFontFamily(xfaFont, node, fontFinder, style) {
const name = stripQuotes(xfaFont.typeface);
const typeface = fontFinder.find(name);
@ -608,6 +615,12 @@ function setFontFamily(xfaFont, fontFinder, style) {
if (fontFamily !== name) {
style.fontFamily = `"${fontFamily}"`;
}
const para = getCurrentPara(node);
if (para && para.lineHeight !== "") {
return;
}
if (style.lineHeight) {
// Already something so don't overwrite.
return;

View file

@ -46,6 +46,8 @@ import {
$nodeName,
$onChild,
$onText,
$popPara,
$pushPara,
$removeChild,
$searchNode,
$setSetAttributes,
@ -1062,8 +1064,11 @@ class Caption extends XFAObject {
return HTMLResult.EMPTY;
}
this[$pushPara]();
const value = this.value[$toHTML](availableSpace).html;
if (!value) {
this[$popPara]();
return HTMLResult.EMPTY;
}
@ -1110,6 +1115,7 @@ class Caption extends XFAObject {
}
setPara(this, null, value);
this[$popPara]();
this.reserve = savedReserve;
@ -1730,6 +1736,7 @@ class Draw extends XFAObject {
}
fixDimensions(this);
this[$pushPara]();
// If at least one dimension is missing and we've a text
// then we can guess it in laying out the text.
@ -1744,6 +1751,7 @@ class Draw extends XFAObject {
// So if we've potentially more width to provide in some parent containers
// let's increase it to give a chance to have a better rendering.
if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) {
this[$popPara]();
return HTMLResult.FAILURE;
}
@ -1757,6 +1765,7 @@ class Draw extends XFAObject {
if (!checkDimensions(this, availableSpace)) {
this.w = savedW;
this.h = savedH;
this[$popPara]();
return HTMLResult.FAILURE;
}
unsetFirstUnsplittable(this);
@ -1816,6 +1825,7 @@ class Draw extends XFAObject {
if (value === null) {
this.w = savedW;
this.h = savedH;
this[$popPara]();
return HTMLResult.success(createWrapper(this, html), bbox);
}
@ -1825,6 +1835,7 @@ class Draw extends XFAObject {
this.w = savedW;
this.h = savedH;
this[$popPara]();
return HTMLResult.success(createWrapper(this, html), bbox);
}
}
@ -2364,6 +2375,7 @@ class ExclGroup extends XFAObject {
attributes.xfaName = this.name;
}
this[$pushPara]();
const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb";
const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1;
for (; this[$extra].attempt < maxRun; this[$extra].attempt++) {
@ -2381,6 +2393,7 @@ class ExclGroup extends XFAObject {
break;
}
if (result.isBreak()) {
this[$popPara]();
return result;
}
if (
@ -2395,6 +2408,8 @@ class ExclGroup extends XFAObject {
}
}
this[$popPara]();
if (!isSplittable) {
unsetFirstUnsplittable(this);
}
@ -2627,6 +2642,8 @@ class Field extends XFAObject {
delete this.caption[$extra];
}
this[$pushPara]();
const caption = this.caption
? this.caption[$toHTML](availableSpace).html
: null;
@ -2667,6 +2684,7 @@ class Field extends XFAObject {
// See comment in Draw::[$toHTML] to have an explanation
// about this line.
if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) {
this[$popPara]();
return HTMLResult.FAILURE;
}
@ -2706,12 +2724,15 @@ class Field extends XFAObject {
}
}
this[$popPara]();
fixDimensions(this);
setFirstUnsplittable(this);
if (!checkDimensions(this, availableSpace)) {
this.w = savedW;
this.h = savedH;
this[$popPara]();
return HTMLResult.FAILURE;
}
unsetFirstUnsplittable(this);
@ -3131,7 +3152,7 @@ class Font extends XFAObject {
style.fontStyle = this.posture;
style.fontSize = measureToString(0.99 * this.size);
setFontFamily(this, this[$globalData].fontFinder, style);
setFontFamily(this, this, this[$globalData].fontFinder, style);
if (this.underline !== 0) {
style.textDecoration = "underline";
@ -4957,6 +4978,7 @@ class Subform extends XFAObject {
}
}
this[$pushPara]();
const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb";
const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1;
for (; this[$extra].attempt < maxRun; this[$extra].attempt++) {
@ -4974,6 +4996,7 @@ class Subform extends XFAObject {
break;
}
if (result.isBreak()) {
this[$popPara]();
return result;
}
if (
@ -4995,6 +5018,7 @@ class Subform extends XFAObject {
}
}
this[$popPara]();
if (!isSplittable) {
unsetFirstUnsplittable(this);
}
@ -5242,6 +5266,7 @@ class Template extends XFAObject {
pagePosition: "first",
oddOrEven: "odd",
blankOrNotBlank: "nonBlank",
paraStack: [],
};
const root = this.subform.children[0];

View file

@ -15,7 +15,7 @@
import { selectFont } from "./fonts.js";
const WIDTH_FACTOR = 1.01;
const WIDTH_FACTOR = 1.02;
class FontInfo {
constructor(xfaFont, margin, lineHeight, fontFinder) {
@ -188,22 +188,25 @@ class TextMeasure {
const noGap = fontLineHeight - lineGap;
const firstLineHeight = Math.max(1, noGap) * fontSize;
const scale = fontSize / 1000;
const fallbackWidth =
pdfFont.defaultWidth || pdfFont.charsToGlyphs(" ")[0].width;
for (const line of str.split(/[\u2029\n]/)) {
const encodedLine = pdfFont.encodeString(line).join("");
const glyphs = pdfFont.charsToGlyphs(encodedLine);
for (const glyph of glyphs) {
const width = glyph.width || fallbackWidth;
this.glyphs.push([
glyph.width * scale + letterSpacing,
width * scale + letterSpacing,
lineHeight,
firstLineHeight,
glyph.unicode === " ",
glyph.unicode,
false,
]);
}
this.glyphs.push([0, 0, 0, false, true]);
this.glyphs.push([0, 0, 0, "\n", true]);
}
this.glyphs.pop();
return;
@ -212,16 +215,10 @@ class TextMeasure {
// When we have no font in the pdf, just use the font size as default width.
for (const line of str.split(/[\u2029\n]/)) {
for (const char of line.split("")) {
this.glyphs.push([
fontSize,
1.2 * fontSize,
fontSize,
char === " ",
false,
]);
this.glyphs.push([fontSize, 1.2 * fontSize, fontSize, char, false]);
}
this.glyphs.push([0, 0, 0, false, true]);
this.glyphs.push([0, 0, 0, "\n", true]);
}
this.glyphs.pop();
}
@ -237,8 +234,9 @@ class TextMeasure {
let isFirstLine = true;
for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
const [glyphWidth, lineHeight, firstLineHeight, isSpace, isEOL] =
const [glyphWidth, lineHeight, firstLineHeight, char, isEOL] =
this.glyphs[i];
const isSpace = char === " ";
const glyphHeight = isFirstLine ? firstLineHeight : lineHeight;
if (isEOL) {
width = Math.max(width, currentLineWidth);

View file

@ -73,6 +73,8 @@ const $onChild = Symbol();
const $onChildCheck = Symbol();
const $onText = Symbol();
const $pushGlyphs = Symbol();
const $popPara = Symbol();
const $pushPara = Symbol();
const $removeChild = Symbol();
const $root = Symbol("root");
const $resolvePrototypes = Symbol();
@ -177,6 +179,16 @@ class XFAObject {
return false;
}
[$popPara]() {
if (this.para) {
this[$getTemplateRoot]()[$extra].paraStack.pop();
}
}
[$pushPara]() {
this[$getTemplateRoot]()[$extra].paraStack.push(this.para);
}
[$setId](ids) {
if (this.id && this[$namespaceId] === NamespaceIds.template.id) {
ids.set(this.id, this);
@ -1109,7 +1121,9 @@ export {
$onChild,
$onChildCheck,
$onText,
$popPara,
$pushGlyphs,
$pushPara,
$removeChild,
$resolvePrototypes,
$root,

View file

@ -105,7 +105,7 @@ const StyleMapping = new Map([
const spacesRegExp = /\s+/g;
const crlfRegExp = /[\r\n]+/g;
function mapStyle(styleStr, fontFinder) {
function mapStyle(styleStr, node) {
const style = Object.create(null);
if (!styleStr) {
return style;
@ -144,7 +144,8 @@ function mapStyle(styleStr, fontFinder) {
posture: style.fontStyle || "normal",
size: original.fontSize || 0,
},
fontFinder,
node,
node[$globalData].fontFinder,
style
);
}
@ -309,7 +310,7 @@ class XhtmlObject extends XmlObject {
name: this[$nodeName],
attributes: {
href: this.href,
style: mapStyle(this.style, this[$globalData].fontFinder),
style: mapStyle(this.style, this),
},
children,
value: this[$content] || "",