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:
parent
ac5c4b7fd0
commit
4a4591bd2c
17 changed files with 1614 additions and 1248 deletions
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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] || "",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue