1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 06:38:07 +02:00

[api-major] Apply the userUnit using CSS, to fix the text/annotation layers (bug 1947248)

Rather than modifying the "raw" dimensions of the page, we'll instead apply the `userUnit` as an *additional* scale-factor via CSS.

*Please note:* It's not clear to me if this solution is fully correct either, or if there's other problems with it, but it at least *appears* to work.

---

With these changes, the following CSS variables are now assumed to be available/set as necessary: `--total-scale-factor`, `--scale-factor`, `--user-unit`, `--scale-round-x`, and `--scale-round-y`.
This commit is contained in:
Jonas Jenwald 2025-02-11 11:55:24 +01:00
parent e3cca6d513
commit bd05b255fa
16 changed files with 237 additions and 46 deletions

View file

@ -1421,7 +1421,7 @@ class ChoiceList extends XFAObject {
const field = ui[$getParent](); const field = ui[$getParent]();
const fontSize = field.font?.size || 10; const fontSize = field.font?.size || 10;
const optionStyle = { const optionStyle = {
fontSize: `calc(${fontSize}px * var(--scale-factor))`, fontSize: `calc(${fontSize}px * var(--total-scale-factor))`,
}; };
const children = []; const children = [];

View file

@ -179,7 +179,7 @@ function mapStyle(styleStr, node, richText) {
} }
if (richText && style.fontSize) { if (richText && style.fontSize) {
style.fontSize = `calc(${style.fontSize} * var(--scale-factor))`; style.fontSize = `calc(${style.fontSize} * var(--total-scale-factor))`;
} }
fixTextIndent(style); fixTextIndent(style);

View file

@ -297,10 +297,10 @@ class AnnotationElement {
const horizontalRadius = data.borderStyle.horizontalCornerRadius; const horizontalRadius = data.borderStyle.horizontalCornerRadius;
const verticalRadius = data.borderStyle.verticalCornerRadius; const verticalRadius = data.borderStyle.verticalCornerRadius;
if (horizontalRadius > 0 || verticalRadius > 0) { if (horizontalRadius > 0 || verticalRadius > 0) {
const radius = `calc(${horizontalRadius}px * var(--scale-factor)) / calc(${verticalRadius}px * var(--scale-factor))`; const radius = `calc(${horizontalRadius}px * var(--total-scale-factor)) / calc(${verticalRadius}px * var(--total-scale-factor))`;
style.borderRadius = radius; style.borderRadius = radius;
} else if (this instanceof RadioButtonWidgetAnnotationElement) { } else if (this instanceof RadioButtonWidgetAnnotationElement) {
const radius = `calc(${width}px * var(--scale-factor)) / calc(${height}px * var(--scale-factor))`; const radius = `calc(${width}px * var(--total-scale-factor)) / calc(${height}px * var(--total-scale-factor))`;
style.borderRadius = radius; style.borderRadius = radius;
} }
@ -1194,7 +1194,7 @@ class WidgetAnnotationElement extends AnnotationElement {
roundToOneDecimal(height / LINE_FACTOR) roundToOneDecimal(height / LINE_FACTOR)
); );
} }
style.fontSize = `calc(${computedFontSize}px * var(--scale-factor))`; style.fontSize = `calc(${computedFontSize}px * var(--total-scale-factor))`;
style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]); style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]);
@ -1553,7 +1553,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
const combWidth = fieldWidth / maxLen; const combWidth = fieldWidth / maxLen;
element.classList.add("comb"); element.classList.add("comb");
element.style.letterSpacing = `calc(${combWidth}px * var(--scale-factor) - 1ch)`; element.style.letterSpacing = `calc(${combWidth}px * var(--total-scale-factor) - 1ch)`;
} }
} else { } else {
element = document.createElement("div"); element = document.createElement("div");
@ -2279,7 +2279,7 @@ class PopupElement {
style: { style: {
color: this.#fontColor, color: this.#fontColor,
fontSize: this.#fontSize fontSize: this.#fontSize
? `calc(${this.#fontSize}px * var(--scale-factor))` ? `calc(${this.#fontSize}px * var(--total-scale-factor))`
: "", : "",
}, },
}; };

View file

@ -213,8 +213,7 @@ class PageViewport {
* @type {Object} * @type {Object}
*/ */
get rawDims() { get rawDims() {
const { userUnit, viewBox } = this; const dims = this.viewBox;
const dims = viewBox.map(x => x * userUnit);
return shadow(this, "rawDims", { return shadow(this, "rawDims", {
pageWidth: dims[2] - dims[0], pageWidth: dims[2] - dims[0],
@ -597,13 +596,13 @@ function setLayerDimensions(
const { style } = div; const { style } = div;
const useRound = FeatureTest.isCSSRoundSupported; const useRound = FeatureTest.isCSSRoundSupported;
const w = `var(--scale-factor) * ${pageWidth}px`, const w = `var(--total-scale-factor) * ${pageWidth}px`,
h = `var(--scale-factor) * ${pageHeight}px`; h = `var(--total-scale-factor) * ${pageHeight}px`;
const widthStr = useRound const widthStr = useRound
? `round(down, ${w}, var(--scale-round-x, 1px))` ? `round(down, ${w}, var(--scale-round-x))`
: `calc(${w})`, : `calc(${w})`,
heightStr = useRound heightStr = useRound
? `round(down, ${h}, var(--scale-round-y, 1px))` ? `round(down, ${h}, var(--scale-round-y))`
: `calc(${h})`; : `calc(${h})`;
if (!mustFlip || viewport.rotation % 180 === 0) { if (!mustFlip || viewport.rotation % 180 === 0) {

View file

@ -209,7 +209,7 @@ class FreeTextEditor extends AnnotationEditor {
*/ */
#updateFontSize(fontSize) { #updateFontSize(fontSize) {
const setFontsize = size => { const setFontsize = size => {
this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`; this.editorDiv.style.fontSize = `calc(${size}px * var(--total-scale-factor))`;
this.translate(0, -(size - this.#fontSize) * this.parentScale); this.translate(0, -(size - this.#fontSize) * this.parentScale);
this.#fontSize = size; this.#fontSize = size;
this.#setEditorDimensions(); this.#setEditorDimensions();
@ -570,7 +570,7 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.contentEditable = true; this.editorDiv.contentEditable = true;
const { style } = this.editorDiv; const { style } = this.editorDiv;
style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`; style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`;
style.color = this.#color; style.color = this.#color;
this.div.append(this.editorDiv); this.div.append(this.editorDiv);
@ -878,7 +878,7 @@ class FreeTextEditor extends AnnotationEditor {
return content; return content;
} }
const { style } = content; const { style } = content;
style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`; style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`;
style.color = this.#color; style.color = this.#color;
content.replaceChildren(); content.replaceChildren();

View file

@ -342,7 +342,7 @@ class TextLayer {
top = tx[5] - fontAscent * Math.cos(angle); top = tx[5] - fontAscent * Math.cos(angle);
} }
const scaleFactorStr = "calc(var(--scale-factor)*"; const scaleFactorStr = "calc(var(--total-scale-factor) *";
const divStyle = textDiv.style; const divStyle = textDiv.style;
// Setting the style properties individually, rather than all at once, // Setting the style properties individually, rather than all at once,
// should be OK since the `textDiv` isn't appended to the document yet. // should be OK since the `textDiv` isn't appended to the document yet.

View file

@ -215,6 +215,18 @@ class Rasterize {
return { svg, foreignObject, style, div }; return { svg, foreignObject, style, div };
} }
static createRootCSS(viewport) {
const { scale, userUnit } = viewport;
return [
":root {",
" --scale-round-x: 1px; --scale-round-y: 1px;",
` --scale-factor: ${scale};`,
` --user-unit: ${userUnit};`,
` --total-scale-factor: ${scale * userUnit};`,
"}",
].join("\n");
}
static async annotationLayer( static async annotationLayer(
ctx, ctx,
viewport, viewport,
@ -232,9 +244,7 @@ class Rasterize {
div.className = "annotationLayer"; div.className = "annotationLayer";
const [common, overrides] = await this.annotationStylePromise; const [common, overrides] = await this.annotationStylePromise;
style.textContent = style.textContent = `${common}\n${overrides}\n${this.createRootCSS(viewport)}`;
`${common}\n${overrides}\n` +
`:root { --scale-factor: ${viewport.scale} }`;
const annotationViewport = viewport.clone({ dontFlip: true }); const annotationViewport = viewport.clone({ dontFlip: true });
const annotationImageMap = await convertCanvasesToImages( const annotationImageMap = await convertCanvasesToImages(
@ -293,9 +303,7 @@ class Rasterize {
svg.setAttribute("font-size", 1); svg.setAttribute("font-size", 1);
const [common, overrides] = await this.textStylePromise; const [common, overrides] = await this.textStylePromise;
style.textContent = style.textContent = `${common}\n${overrides}\n${this.createRootCSS(viewport)}`;
`${common}\n${overrides}\n` +
`:root { --scale-factor: ${viewport.scale} }`;
// Rendering text layer as HTML. // Rendering text layer as HTML.
const textLayer = new TextLayer({ const textLayer = new TextLayer({
@ -322,9 +330,7 @@ class Rasterize {
svg.setAttribute("font-size", 1); svg.setAttribute("font-size", 1);
const [common, overrides] = await this.drawLayerStylePromise; const [common, overrides] = await this.drawLayerStylePromise;
style.textContent = style.textContent = `${common}\n${overrides}\n${this.createRootCSS(viewport)}`;
`${common}\n${overrides}` +
`:root { --scale-factor: ${viewport.scale} }`;
// Rendering text layer as HTML. // Rendering text layer as HTML.
const textLayer = new TextLayer({ const textLayer = new TextLayer({
@ -346,9 +352,9 @@ class Rasterize {
let x = parseFloat(left) / 100; let x = parseFloat(left) / 100;
let y = parseFloat(top) / 100; let y = parseFloat(top) / 100;
if (isNaN(x)) { if (isNaN(x)) {
posRegex ||= /^calc\(var\(--scale-factor\)\*(.*)px\)$/; posRegex ||= /^calc\(var\(--total-scale-factor\)\s*\*(.*)px\)$/;
// The element is tagged so we've to extract the position from the // The element is tagged so we've to extract the position from the
// string, e.g. `calc(var(--scale-factor)*66.32px)`. // string, e.g. `calc(var(--total-scale-factor)*66.32px)`.
let match = left.match(posRegex); let match = left.match(posRegex);
if (match) { if (match) {
x = parseFloat(match[1]) / pageWidth; x = parseFloat(match[1]) / pageWidth;

View file

@ -559,6 +559,8 @@
!poppler-85140-0.pdf !poppler-85140-0.pdf
!issue15012.pdf !issue15012.pdf
!issue19176.pdf !issue19176.pdf
!bug1947248_text.pdf
!bug1947248_forms.pdf
!issue15150.pdf !issue15150.pdf
!poppler-395-0-fuzzed.pdf !poppler-395-0-fuzzed.pdf
!issue14165.pdf !issue14165.pdf

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -2749,6 +2749,21 @@
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{
"id": "bug1947248-text",
"file": "pdfs/bug1947248_text.pdf",
"md5": "491f1df75b77d2762ff96ce51f5e019b",
"rounds": 1,
"type": "text"
},
{
"id": "bug1947248-forms",
"file": "pdfs/bug1947248_forms.pdf",
"md5": "456c974d7d4351719f36ef10e603d29c",
"rounds": 1,
"type": "eq",
"forms": true
},
{ {
"id": "issue4801", "id": "issue4801",
"file": "pdfs/issue4801.pdf", "file": "pdfs/issue4801.pdf",

View file

@ -123,7 +123,7 @@
background: transparent; background: transparent;
position: absolute; position: absolute;
inset: 0; inset: 0;
font-size: calc(100px * var(--scale-factor)); font-size: calc(100px * var(--total-scale-factor));
transform-origin: 0 0; transform-origin: 0 0;
cursor: auto; cursor: auto;
@ -512,7 +512,7 @@
} }
.annotationEditorLayer .freeTextEditor { .annotationEditorLayer .freeTextEditor {
padding: calc(var(--freetext-padding) * var(--scale-factor)); padding: calc(var(--freetext-padding) * var(--total-scale-factor));
width: auto; width: auto;
height: auto; height: auto;
touch-action: none; touch-action: none;

View file

@ -50,7 +50,7 @@
} }
.popupAnnotation .popup { .popupAnnotation .popup {
outline: calc(1.5px * var(--scale-factor)) solid CanvasText !important; outline: calc(1.5px * var(--total-scale-factor)) solid CanvasText !important;
background-color: ButtonFace !important; background-color: ButtonFace !important;
color: ButtonText !important; color: ButtonText !important;
} }
@ -67,7 +67,7 @@
} }
.popupAnnotation.focused .popup { .popupAnnotation.focused .popup {
outline: calc(3px * var(--scale-factor)) solid Highlight !important; outline: calc(3px * var(--total-scale-factor)) solid Highlight !important;
} }
} }
@ -169,7 +169,7 @@
background-image: var(--annotation-unfocused-field-background); background-image: var(--annotation-unfocused-field-background);
border: 2px solid var(--input-unfocused-border-color); border: 2px solid var(--input-unfocused-border-color);
box-sizing: border-box; box-sizing: border-box;
font: calc(9px * var(--scale-factor)) sans-serif; font: calc(9px * var(--total-scale-factor)) sans-serif;
height: 100%; height: 100%;
margin: 0; margin: 0;
vertical-align: top; vertical-align: top;
@ -296,7 +296,7 @@
.popupAnnotation { .popupAnnotation {
position: absolute; position: absolute;
font-size: calc(9px * var(--scale-factor)); font-size: calc(9px * var(--total-scale-factor));
pointer-events: none; pointer-events: none;
width: max-content; width: max-content;
max-width: 45%; max-width: 45%;
@ -305,11 +305,11 @@
.popup { .popup {
background-color: rgb(255 255 153); background-color: rgb(255 255 153);
box-shadow: 0 calc(2px * var(--scale-factor)) box-shadow: 0 calc(2px * var(--total-scale-factor))
calc(5px * var(--scale-factor)) rgb(136 136 136); calc(5px * var(--total-scale-factor)) rgb(136 136 136);
border-radius: calc(2px * var(--scale-factor)); border-radius: calc(2px * var(--total-scale-factor));
outline: 1.5px solid rgb(255 255 74); outline: 1.5px solid rgb(255 255 74);
padding: calc(6px * var(--scale-factor)); padding: calc(6px * var(--total-scale-factor));
cursor: pointer; cursor: pointer;
font: message-box; font: message-box;
white-space: normal; white-space: normal;
@ -323,7 +323,7 @@
} }
.popup * { .popup * {
font-size: calc(9px * var(--scale-factor)); font-size: calc(9px * var(--total-scale-factor));
} }
.popup > .header { .popup > .header {
@ -336,19 +336,19 @@
.popup > .header .popupDate { .popup > .header .popupDate {
display: inline-block; display: inline-block;
margin-left: calc(5px * var(--scale-factor)); margin-left: calc(5px * var(--total-scale-factor));
width: fit-content; width: fit-content;
} }
.popupContent { .popupContent {
border-top: 1px solid rgb(51 51 51); border-top: 1px solid rgb(51 51 51);
margin-top: calc(2px * var(--scale-factor)); margin-top: calc(2px * var(--total-scale-factor));
padding-top: calc(2px * var(--scale-factor)); padding-top: calc(2px * var(--total-scale-factor));
} }
.richText > * { .richText > * {
white-space: pre-wrap; white-space: pre-wrap;
font-size: calc(9px * var(--scale-factor)); font-size: calc(9px * var(--total-scale-factor));
} }
.popupTriggerArea { .popupTriggerArea {

View file

@ -147,6 +147,8 @@ class PDFPageView {
#textLayerMode = TextLayerMode.ENABLE; #textLayerMode = TextLayerMode.ENABLE;
#userUnit = 1;
#useThumbnailCanvas = { #useThumbnailCanvas = {
directDrawing: true, directDrawing: true,
initialOptionalContent: true, initialOptionalContent: true,
@ -314,7 +316,16 @@ class PDFPageView {
} }
#setDimensions() { #setDimensions() {
const { viewport } = this; const { div, viewport } = this;
if (viewport.userUnit !== this.#userUnit) {
if (viewport.userUnit !== 1) {
div.style.setProperty("--user-unit", viewport.userUnit);
} else {
div.style.removeProperty("--user-unit");
}
this.#userUnit = viewport.userUnit;
}
if (this.pdfPage) { if (this.pdfPage) {
if (this.#previousRotation === viewport.rotation) { if (this.#previousRotation === viewport.rotation) {
return; return;
@ -323,7 +334,7 @@ class PDFPageView {
} }
setLayerDimensions( setLayerDimensions(
this.div, div,
viewport, viewport,
/* mustFlip = */ true, /* mustFlip = */ true,
/* mustRotate = */ false /* mustRotate = */ false

View file

@ -100,6 +100,8 @@
} }
.pdfViewer .page { .pdfViewer .page {
--user-unit: 1;
--total-scale-factor: calc(var(--scale-factor) * var(--user-unit));
--scale-round-x: 1px; --scale-round-x: 1px;
--scale-round-y: 1px; --scale-round-y: 1px;

View file

@ -204,7 +204,7 @@ class StructTreeLayerBuilder {
img.setAttribute("aria-label", removeNullCharacters(alt)); img.setAttribute("aria-label", removeNullCharacters(alt));
const { pageHeight, pageX, pageY } = this.#rawDims; const { pageHeight, pageX, pageY } = this.#rawDims;
const calc = "calc(var(--scale-factor)*"; const calc = "calc(var(--total-scale-factor) *";
const { style } = img; const { style } = img;
style.width = `${calc}${bbox[2] - bbox[0]}px)`; style.width = `${calc}${bbox[2] - bbox[0]}px)`;
style.height = `${calc}${bbox[3] - bbox[1]}px)`; style.height = `${calc}${bbox[3] - bbox[1]}px)`;