mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-28 23:28:16 +02:00
XFA - Add the possibily to layout and measure text
- some containers doesn't always have their 2 dimensions and those dimensions re based on contents; - so in order to measure text, we must get the glyph widths (for the xfa fonts) before starting the layout; - implement a word-wrap algorithm; - handle font change during text layout.
This commit is contained in:
parent
335d4cb2fc
commit
8eeb7ab4a3
12 changed files with 416 additions and 91 deletions
218
src/core/xfa/text.js
Normal file
218
src/core/xfa/text.js
Normal file
|
@ -0,0 +1,218 @@
|
|||
/* Copyright 2021 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const WIDTH_FACTOR = 1.2;
|
||||
const HEIGHT_FACTOR = 1.2;
|
||||
|
||||
class FontInfo {
|
||||
constructor(xfaFont, fonts) {
|
||||
if (!xfaFont) {
|
||||
[this.pdfFont, this.xfaFont] = this.defaultFont(fonts);
|
||||
return;
|
||||
}
|
||||
|
||||
this.xfaFont = xfaFont;
|
||||
let typeface = fonts[xfaFont.typeface];
|
||||
if (!typeface) {
|
||||
typeface = fonts[`${xfaFont.typeface}-PdfJS-XFA`];
|
||||
}
|
||||
if (!typeface) {
|
||||
[this.pdfFont, this.xfaFont] = this.defaultFont(fonts);
|
||||
return;
|
||||
}
|
||||
|
||||
this.pdfFont = null;
|
||||
if (xfaFont.posture === "italic") {
|
||||
if (xfaFont.weight === "bold") {
|
||||
this.pdfFont = typeface.bolditalic;
|
||||
} else {
|
||||
this.pdfFont = typeface.italic;
|
||||
}
|
||||
} else if (xfaFont.weigth === "bold") {
|
||||
this.pdfFont = typeface.bold;
|
||||
} else {
|
||||
this.pdfFont = typeface.regular;
|
||||
}
|
||||
|
||||
if (!this.pdfFont) {
|
||||
[this.pdfFont, this.xfaFont] = this.defaultFont(fonts);
|
||||
}
|
||||
}
|
||||
|
||||
defaultFont(fonts) {
|
||||
// TODO: Add a default font based on Liberation.
|
||||
const font =
|
||||
fonts.Helvetica ||
|
||||
fonts["Myriad Pro"] ||
|
||||
fonts.Arial ||
|
||||
fonts.ArialMT ||
|
||||
Object.values(fonts)[0];
|
||||
const pdfFont = font.regular;
|
||||
const info = this.pdfFont.cssFontInfo;
|
||||
const xfaFont = {
|
||||
typeface: info.fontFamily,
|
||||
posture: "normal",
|
||||
weight: "normal",
|
||||
size: 10,
|
||||
};
|
||||
return [pdfFont, xfaFont];
|
||||
}
|
||||
}
|
||||
|
||||
class FontSelector {
|
||||
constructor(defaultXfaFont, fonts) {
|
||||
this.fonts = fonts;
|
||||
this.stack = [new FontInfo(defaultXfaFont, fonts)];
|
||||
}
|
||||
|
||||
pushFont(xfaFont) {
|
||||
const lastFont = this.stack[this.stack.length - 1];
|
||||
for (const name of ["typeface", "posture", "weight", "size"]) {
|
||||
if (!xfaFont[name]) {
|
||||
xfaFont[name] = lastFont.xfaFont[name];
|
||||
}
|
||||
}
|
||||
|
||||
const fontInfo = new FontInfo(xfaFont, this.fonts);
|
||||
if (!fontInfo.pdfFont) {
|
||||
fontInfo.pdfFont = lastFont.pdfFont;
|
||||
}
|
||||
this.stack.push(fontInfo);
|
||||
}
|
||||
|
||||
popFont() {
|
||||
this.stack.pop();
|
||||
}
|
||||
|
||||
topFont() {
|
||||
return this.stack[this.stack.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a text area dimensions based on font metrics.
|
||||
*/
|
||||
class TextMeasure {
|
||||
constructor(defaultXfaFont, fonts) {
|
||||
this.glyphs = [];
|
||||
this.fontSelector = new FontSelector(defaultXfaFont, fonts);
|
||||
}
|
||||
|
||||
pushFont(xfaFont) {
|
||||
return this.fontSelector.pushFont(xfaFont);
|
||||
}
|
||||
|
||||
popFont(xfaFont) {
|
||||
return this.fontSelector.popFont();
|
||||
}
|
||||
|
||||
addString(str) {
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastFont = this.fontSelector.topFont();
|
||||
const pdfFont = lastFont.pdfFont;
|
||||
const fontSize = lastFont.xfaFont.size;
|
||||
const lineHeight = Math.round(Math.max(1, pdfFont.lineHeight) * fontSize);
|
||||
const scale = fontSize / 1000;
|
||||
|
||||
for (const line of str.split(/[\u2029\n]/)) {
|
||||
const encodedLine = pdfFont.encodeString(line).join("");
|
||||
const glyphs = pdfFont.charsToGlyphs(encodedLine);
|
||||
|
||||
for (const glyph of glyphs) {
|
||||
this.glyphs.push([
|
||||
glyph.width * scale,
|
||||
lineHeight,
|
||||
glyph.unicode === " ",
|
||||
false,
|
||||
]);
|
||||
}
|
||||
|
||||
this.glyphs.push([0, 0, false, true]);
|
||||
}
|
||||
|
||||
this.glyphs.pop();
|
||||
}
|
||||
|
||||
compute(maxWidth) {
|
||||
let lastSpacePos = -1,
|
||||
lastSpaceWidth = 0,
|
||||
width = 0,
|
||||
height = 0,
|
||||
currentLineWidth = 0,
|
||||
currentLineHeight = 0;
|
||||
|
||||
for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
|
||||
const [glyphWidth, glyphHeight, isSpace, isEOL] = this.glyphs[i];
|
||||
if (isEOL) {
|
||||
width = Math.max(width, currentLineWidth);
|
||||
currentLineWidth = 0;
|
||||
height += currentLineHeight;
|
||||
currentLineHeight = glyphHeight;
|
||||
lastSpacePos = -1;
|
||||
lastSpaceWidth = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isSpace) {
|
||||
if (currentLineWidth + glyphWidth > maxWidth) {
|
||||
// We can break here but the space is not taken into account.
|
||||
width = Math.max(width, currentLineWidth);
|
||||
currentLineWidth = 0;
|
||||
height += currentLineHeight;
|
||||
currentLineHeight = glyphHeight;
|
||||
lastSpacePos = -1;
|
||||
lastSpaceWidth = 0;
|
||||
} else {
|
||||
currentLineHeight = Math.max(glyphHeight, currentLineHeight);
|
||||
lastSpaceWidth = currentLineWidth;
|
||||
currentLineWidth += glyphWidth;
|
||||
lastSpacePos = i;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentLineWidth + glyphWidth > maxWidth) {
|
||||
// We must break to the last white position (if available)
|
||||
height += currentLineHeight;
|
||||
currentLineHeight = glyphHeight;
|
||||
if (lastSpacePos !== -1) {
|
||||
i = lastSpacePos;
|
||||
width = Math.max(width, lastSpaceWidth);
|
||||
currentLineWidth = 0;
|
||||
lastSpacePos = -1;
|
||||
lastSpaceWidth = 0;
|
||||
} else {
|
||||
// Just break in the middle of the word
|
||||
width = Math.max(width, currentLineWidth);
|
||||
currentLineWidth = glyphWidth;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
currentLineWidth += glyphWidth;
|
||||
currentLineHeight = Math.max(glyphHeight, currentLineHeight);
|
||||
}
|
||||
|
||||
width = Math.max(width, currentLineWidth);
|
||||
height += currentLineHeight;
|
||||
|
||||
return { width: WIDTH_FACTOR * width, height: HEIGHT_FACTOR * height };
|
||||
}
|
||||
}
|
||||
|
||||
export { TextMeasure };
|
Loading…
Add table
Add a link
Reference in a new issue