mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-26 10:08:06 +02:00
XFA - Fix auto-sized fields (bug 1722030)
- In order to better compute text fields size, use line height with no gaps (and consequently guessed height for text are slightly better in general). - Fix default background color in fields.
This commit is contained in:
parent
336a74a0e5
commit
76d882b560
14 changed files with 311 additions and 162 deletions
|
@ -13,6 +13,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { $globalData } from "./xfa_object.js";
|
||||
import { stripQuotes } from "./utils.js";
|
||||
import { warn } from "../../shared/util.js";
|
||||
|
||||
class FontFinder {
|
||||
|
@ -178,4 +180,32 @@ function selectFont(xfaFont, typeface) {
|
|||
return typeface.regular;
|
||||
}
|
||||
|
||||
export { FontFinder, selectFont };
|
||||
function getMetrics(xfaFont, real = false) {
|
||||
let pdfFont = null;
|
||||
if (xfaFont) {
|
||||
const name = stripQuotes(xfaFont.typeface);
|
||||
const typeface = xfaFont[$globalData].fontFinder.find(name);
|
||||
pdfFont = selectFont(xfaFont, typeface);
|
||||
}
|
||||
|
||||
if (!pdfFont) {
|
||||
return {
|
||||
lineHeight: 12,
|
||||
lineGap: 2,
|
||||
lineNoGap: 10,
|
||||
};
|
||||
}
|
||||
|
||||
const size = xfaFont.size || 10;
|
||||
const lineHeight = pdfFont.lineHeight
|
||||
? Math.max(real ? 0 : 1.2, pdfFont.lineHeight)
|
||||
: 1.2;
|
||||
const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
|
||||
return {
|
||||
lineHeight: lineHeight * size,
|
||||
lineGap: lineGap * size,
|
||||
lineNoGap: Math.max(1, lineHeight - lineGap) * size,
|
||||
};
|
||||
}
|
||||
|
||||
export { FontFinder, getMetrics, selectFont };
|
||||
|
|
|
@ -247,7 +247,7 @@ function layoutNode(node, availableSpace) {
|
|||
}
|
||||
}
|
||||
|
||||
const maxWidth = !node.w ? availableSpace.width : node.w;
|
||||
const maxWidth = (!node.w ? availableSpace.width : node.w) - marginH;
|
||||
const fontFinder = node[$globalData].fontFinder;
|
||||
if (
|
||||
node.value.exData &&
|
||||
|
@ -614,10 +614,8 @@ function setFontFamily(xfaFont, fontFinder, style) {
|
|||
}
|
||||
|
||||
const pdfFont = selectFont(xfaFont, typeface);
|
||||
if (pdfFont && pdfFont.lineHeight > 0) {
|
||||
if (pdfFont) {
|
||||
style.lineHeight = Math.max(1.2, pdfFont.lineHeight);
|
||||
} else {
|
||||
style.lineHeight = 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ import {
|
|||
HTMLResult,
|
||||
} from "./utils.js";
|
||||
import { stringToBytes, Util, warn } from "../../shared/util.js";
|
||||
import { getMetrics } from "./fonts.js";
|
||||
import { searchNode } from "./som.js";
|
||||
|
||||
const TEMPLATE_NS_ID = NamespaceIds.template.id;
|
||||
|
@ -115,6 +116,30 @@ const MAX_ATTEMPTS_FOR_LRTB_LAYOUT = 2;
|
|||
// the loop after having MAX_EMPTY_PAGES empty pages.
|
||||
const MAX_EMPTY_PAGES = 3;
|
||||
|
||||
function getBorderDims(node) {
|
||||
if (!node || !node.border) {
|
||||
return { w: 0, h: 0 };
|
||||
}
|
||||
|
||||
const borderExtra = node.border[$getExtra]();
|
||||
if (!borderExtra) {
|
||||
return { w: 0, h: 0 };
|
||||
}
|
||||
|
||||
return {
|
||||
w:
|
||||
borderExtra.widths[0] +
|
||||
borderExtra.widths[2] +
|
||||
borderExtra.insets[0] +
|
||||
borderExtra.insets[2],
|
||||
h:
|
||||
borderExtra.widths[1] +
|
||||
borderExtra.widths[3] +
|
||||
borderExtra.insets[1] +
|
||||
borderExtra.insets[3],
|
||||
};
|
||||
}
|
||||
|
||||
function hasMargin(node) {
|
||||
return (
|
||||
node.margin &&
|
||||
|
@ -773,33 +798,38 @@ class Border extends XFAObject {
|
|||
this.margin = null;
|
||||
}
|
||||
|
||||
[$toStyle]() {
|
||||
// TODO: incomplete.
|
||||
const edges = this.edge.children.slice();
|
||||
if (edges.length < 4) {
|
||||
const defaultEdge = edges[edges.length - 1] || new Edge({});
|
||||
for (let i = edges.length; i < 4; i++) {
|
||||
edges.push(defaultEdge);
|
||||
[$getExtra]() {
|
||||
if (!this[$extra]) {
|
||||
const edges = this.edge.children.slice();
|
||||
if (edges.length < 4) {
|
||||
const defaultEdge = edges[edges.length - 1] || new Edge({});
|
||||
for (let i = edges.length; i < 4; i++) {
|
||||
edges.push(defaultEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const widths = edges.map(edge => edge.thickness);
|
||||
const insets = [0, 0, 0, 0];
|
||||
if (this.margin) {
|
||||
insets[0] = this.margin.topInset;
|
||||
insets[1] = this.margin.rightInset;
|
||||
insets[2] = this.margin.bottomInset;
|
||||
insets[3] = this.margin.leftInset;
|
||||
}
|
||||
this[$extra] = { widths, insets, edges };
|
||||
}
|
||||
return this[$extra];
|
||||
}
|
||||
|
||||
[$toStyle]() {
|
||||
// TODO: incomplete (hand).
|
||||
const { edges } = this[$getExtra]();
|
||||
const edgeStyles = edges.map(node => {
|
||||
const style = node[$toStyle]();
|
||||
style.color = style.color || "#000000";
|
||||
return style;
|
||||
});
|
||||
|
||||
const widths = edges.map(edge => edge.thickness);
|
||||
const insets = [0, 0, 0, 0];
|
||||
if (this.margin) {
|
||||
insets[0] = this.margin.topInset;
|
||||
insets[1] = this.margin.rightInset;
|
||||
insets[2] = this.margin.bottomInset;
|
||||
insets[3] = this.margin.leftInset;
|
||||
}
|
||||
this[$extra] = { widths, insets };
|
||||
// TODO: hand.
|
||||
|
||||
const style = Object.create(null);
|
||||
if (this.margin) {
|
||||
Object.assign(style, this.margin[$toStyle]());
|
||||
|
@ -2580,7 +2610,6 @@ class Field extends XFAObject {
|
|||
}
|
||||
|
||||
setTabIndex(this);
|
||||
|
||||
if (
|
||||
!this.ui ||
|
||||
this.presence === "hidden" ||
|
||||
|
@ -2603,17 +2632,36 @@ class Field extends XFAObject {
|
|||
: null;
|
||||
const savedW = this.w;
|
||||
const savedH = this.h;
|
||||
if (this.w === "" || this.h === "") {
|
||||
let marginH = 0;
|
||||
let marginV = 0;
|
||||
if (this.margin) {
|
||||
marginH = this.margin.leftInset + this.margin.rightInset;
|
||||
marginV = this.margin.topInset + this.margin.bottomInset;
|
||||
}
|
||||
let marginH = 0;
|
||||
let marginV = 0;
|
||||
if (this.margin) {
|
||||
marginH = this.margin.leftInset + this.margin.rightInset;
|
||||
marginV = this.margin.topInset + this.margin.bottomInset;
|
||||
}
|
||||
|
||||
let borderDims = null;
|
||||
if (this.w === "" || this.h === "") {
|
||||
let width = null;
|
||||
let height = null;
|
||||
|
||||
let uiW = 0;
|
||||
let uiH = 0;
|
||||
if (this.ui.checkButton) {
|
||||
uiW = uiH = this.ui.checkButton.size;
|
||||
} else {
|
||||
const { w, h } = layoutNode(this, availableSpace);
|
||||
if (w !== null) {
|
||||
uiW = w;
|
||||
uiH = h;
|
||||
} else {
|
||||
uiH = getMetrics(this.font, /* real = */ true).lineNoGap;
|
||||
}
|
||||
}
|
||||
|
||||
borderDims = getBorderDims(this.ui[$getExtra]());
|
||||
uiW += borderDims.w;
|
||||
uiH += borderDims.h;
|
||||
|
||||
if (this.caption) {
|
||||
const { w, h, isBroken } = this.caption[$getExtra](availableSpace);
|
||||
// See comment in Draw::[$toHTML] to have an explanation
|
||||
|
@ -2624,32 +2672,36 @@ class Field extends XFAObject {
|
|||
|
||||
width = w;
|
||||
height = h;
|
||||
if (this.ui.checkButton) {
|
||||
switch (this.caption.placement) {
|
||||
case "left":
|
||||
case "right":
|
||||
case "inline":
|
||||
width += this.ui.checkButton.size;
|
||||
break;
|
||||
case "top":
|
||||
case "bottom":
|
||||
height += this.ui.checkButton.size;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (this.caption.placement) {
|
||||
case "left":
|
||||
case "right":
|
||||
case "inline":
|
||||
width += uiW;
|
||||
break;
|
||||
case "top":
|
||||
case "bottom":
|
||||
height += uiH;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
width = uiW;
|
||||
height = uiH;
|
||||
}
|
||||
|
||||
if (width && this.w === "") {
|
||||
width += marginH;
|
||||
this.w = Math.min(
|
||||
this.maxW <= 0 ? Infinity : this.maxW,
|
||||
Math.max(this.minW, width + marginH)
|
||||
this.minW + 1 < width ? width : this.minW
|
||||
);
|
||||
}
|
||||
|
||||
if (height && this.h === "") {
|
||||
height += marginV;
|
||||
this.h = Math.min(
|
||||
this.maxH <= 0 ? Infinity : this.maxH,
|
||||
Math.max(this.minH, height + marginV)
|
||||
this.minH + 1 < height ? height : this.minH
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2718,7 +2770,6 @@ class Field extends XFAObject {
|
|||
}
|
||||
|
||||
const borderStyle = this.border ? this.border[$toStyle]() : null;
|
||||
|
||||
const bbox = computeBbox(this, html, availableSpace);
|
||||
const ui = this.ui[$toHTML]().html;
|
||||
if (!ui) {
|
||||
|
@ -2775,6 +2826,22 @@ class Field extends XFAObject {
|
|||
}
|
||||
}
|
||||
|
||||
if (!this.ui.imageEdit && ui.children && ui.children[0] && this.h) {
|
||||
borderDims = borderDims || getBorderDims(this.ui[$getExtra]());
|
||||
|
||||
let captionHeight = 0;
|
||||
if (this.caption && ["top", "bottom"].includes(this.caption.placement)) {
|
||||
captionHeight = this.caption.reserve;
|
||||
if (captionHeight <= 0) {
|
||||
captionHeight = this.caption[$getExtra](availableSpace).h;
|
||||
}
|
||||
const inputHeight = this.h - captionHeight - marginV - borderDims.h;
|
||||
ui.children[0].attributes.style.height = measureToString(inputHeight);
|
||||
} else {
|
||||
ui.children[0].attributes.style.height = "100%";
|
||||
}
|
||||
}
|
||||
|
||||
if (!caption) {
|
||||
if (ui.attributes.class) {
|
||||
// Even if no caption this class will help to center the ui.
|
||||
|
@ -2803,22 +2870,20 @@ class Field extends XFAObject {
|
|||
ui.attributes.class = [];
|
||||
}
|
||||
|
||||
ui.children.splice(0, 0, caption);
|
||||
|
||||
switch (this.caption.placement) {
|
||||
case "left":
|
||||
ui.children.splice(0, 0, caption);
|
||||
ui.attributes.class.push("xfaLeft");
|
||||
break;
|
||||
case "right":
|
||||
ui.children.push(caption);
|
||||
ui.attributes.class.push("xfaLeft");
|
||||
ui.attributes.class.push("xfaRight");
|
||||
break;
|
||||
case "top":
|
||||
ui.children.splice(0, 0, caption);
|
||||
ui.attributes.class.push("xfaTop");
|
||||
break;
|
||||
case "bottom":
|
||||
ui.children.push(caption);
|
||||
ui.attributes.class.push("xfaTop");
|
||||
ui.attributes.class.push("xfaBottom");
|
||||
break;
|
||||
case "inline":
|
||||
// TODO;
|
||||
|
@ -2857,11 +2922,17 @@ class Fill extends XFAObject {
|
|||
|
||||
[$toStyle]() {
|
||||
const parent = this[$getParent]();
|
||||
const grandpa = parent[$getParent]();
|
||||
const ggrandpa = grandpa[$getParent]();
|
||||
const style = Object.create(null);
|
||||
|
||||
let propName = "color";
|
||||
if (parent instanceof Border) {
|
||||
propName = "background";
|
||||
if (ggrandpa instanceof Ui) {
|
||||
// The default fill color is white.
|
||||
style.background = "white";
|
||||
}
|
||||
}
|
||||
if (parent instanceof Rectangle || parent instanceof Arc) {
|
||||
propName = "fill";
|
||||
|
@ -5515,7 +5586,11 @@ class TextEdit extends XFAObject {
|
|||
"on",
|
||||
]);
|
||||
this.id = attributes.id || "";
|
||||
this.multiLine = attributes.multiLine || "";
|
||||
this.multiLine = getInteger({
|
||||
data: attributes.multiLine,
|
||||
defaultValue: "",
|
||||
validate: x => x === 0 || x === 1,
|
||||
});
|
||||
this.use = attributes.use || "";
|
||||
this.usehref = attributes.usehref || "";
|
||||
this.vScrollPolicy = getStringOption(attributes.vScrollPolicy, [
|
||||
|
@ -5529,22 +5604,14 @@ class TextEdit extends XFAObject {
|
|||
this.margin = null;
|
||||
}
|
||||
|
||||
[$clean](builder) {
|
||||
super[$clean](builder);
|
||||
const parent = this[$getParent]();
|
||||
const defaultValue = parent instanceof Draw ? 1 : 0;
|
||||
this.multiLine = getInteger({
|
||||
data: this.multiLine,
|
||||
defaultValue,
|
||||
validate: x => x === 0 || x === 1,
|
||||
});
|
||||
}
|
||||
|
||||
[$toHTML](availableSpace) {
|
||||
// TODO: incomplete.
|
||||
const style = toStyle(this, "border", "font", "margin");
|
||||
let html;
|
||||
const field = this[$getParent]()[$getParent]();
|
||||
if (this.multiLine === "") {
|
||||
this.multiLine = field instanceof Draw ? 1 : 0;
|
||||
}
|
||||
if (this.multiLine === 1) {
|
||||
html = {
|
||||
name: "textarea",
|
||||
|
@ -5683,17 +5750,29 @@ class Ui extends XFAObject {
|
|||
this.textEdit = null;
|
||||
}
|
||||
|
||||
[$getExtra]() {
|
||||
if (this[$extra] === undefined) {
|
||||
for (const name of Object.getOwnPropertyNames(this)) {
|
||||
if (name === "extras" || name === "picture") {
|
||||
continue;
|
||||
}
|
||||
const obj = this[name];
|
||||
if (!(obj instanceof XFAObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this[$extra] = obj;
|
||||
return obj;
|
||||
}
|
||||
this[$extra] = null;
|
||||
}
|
||||
return this[$extra];
|
||||
}
|
||||
|
||||
[$toHTML](availableSpace) {
|
||||
// TODO: picture.
|
||||
for (const name of Object.getOwnPropertyNames(this)) {
|
||||
if (name === "extras" || name === "picture") {
|
||||
continue;
|
||||
}
|
||||
const obj = this[name];
|
||||
if (!(obj instanceof XFAObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const obj = this[$getExtra]();
|
||||
if (obj) {
|
||||
return obj[$toHTML](availableSpace);
|
||||
}
|
||||
return HTMLResult.EMPTY;
|
||||
|
|
|
@ -181,9 +181,12 @@ class TextMeasure {
|
|||
if (lastFont.pdfFont) {
|
||||
const letterSpacing = lastFont.xfaFont.letterSpacing;
|
||||
const pdfFont = lastFont.pdfFont;
|
||||
const fontLineHeight = pdfFont.lineHeight || 1.2;
|
||||
const lineHeight =
|
||||
lastFont.lineHeight ||
|
||||
Math.ceil(Math.max(1.2, pdfFont.lineHeight) * fontSize);
|
||||
lastFont.lineHeight || Math.max(1.2, fontLineHeight) * fontSize;
|
||||
const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
|
||||
const noGap = fontLineHeight - lineGap;
|
||||
const firstLineHeight = Math.max(1, noGap) * fontSize;
|
||||
const scale = fontSize / 1000;
|
||||
|
||||
for (const line of str.split(/[\u2029\n]/)) {
|
||||
|
@ -194,12 +197,13 @@ class TextMeasure {
|
|||
this.glyphs.push([
|
||||
glyph.width * scale + letterSpacing,
|
||||
lineHeight,
|
||||
firstLineHeight,
|
||||
glyph.unicode === " ",
|
||||
false,
|
||||
]);
|
||||
}
|
||||
|
||||
this.glyphs.push([0, 0, false, true]);
|
||||
this.glyphs.push([0, 0, 0, false, true]);
|
||||
}
|
||||
this.glyphs.pop();
|
||||
return;
|
||||
|
@ -208,10 +212,16 @@ 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, fontSize, char === " ", false]);
|
||||
this.glyphs.push([
|
||||
fontSize,
|
||||
1.2 * fontSize,
|
||||
fontSize,
|
||||
char === " ",
|
||||
false,
|
||||
]);
|
||||
}
|
||||
|
||||
this.glyphs.push([0, 0, false, true]);
|
||||
this.glyphs.push([0, 0, 0, false, true]);
|
||||
}
|
||||
this.glyphs.pop();
|
||||
}
|
||||
|
@ -224,9 +234,12 @@ class TextMeasure {
|
|||
currentLineWidth = 0,
|
||||
currentLineHeight = 0;
|
||||
let isBroken = false;
|
||||
let isFirstLine = true;
|
||||
|
||||
for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
|
||||
const [glyphWidth, glyphHeight, isSpace, isEOL] = this.glyphs[i];
|
||||
const [glyphWidth, lineHeight, firstLineHeight, isSpace, isEOL] =
|
||||
this.glyphs[i];
|
||||
const glyphHeight = isFirstLine ? firstLineHeight : lineHeight;
|
||||
if (isEOL) {
|
||||
width = Math.max(width, currentLineWidth);
|
||||
currentLineWidth = 0;
|
||||
|
@ -234,6 +247,7 @@ class TextMeasure {
|
|||
currentLineHeight = glyphHeight;
|
||||
lastSpacePos = -1;
|
||||
lastSpaceWidth = 0;
|
||||
isFirstLine = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -247,6 +261,7 @@ class TextMeasure {
|
|||
lastSpacePos = -1;
|
||||
lastSpaceWidth = 0;
|
||||
isBroken = true;
|
||||
isFirstLine = false;
|
||||
} else {
|
||||
currentLineHeight = Math.max(glyphHeight, currentLineHeight);
|
||||
lastSpaceWidth = currentLineWidth;
|
||||
|
@ -272,6 +287,7 @@ class TextMeasure {
|
|||
currentLineWidth = glyphWidth;
|
||||
}
|
||||
isBroken = true;
|
||||
isFirstLine = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue