From e9cf1d3a28e031fcedb37867d5c5cc2281770b4f Mon Sep 17 00:00:00 2001 From: Martynas Bagdonas Date: Wed, 13 Dec 2023 19:34:17 +0200 Subject: [PATCH] Add full support for text annotations in rotated pages Fixes zotero/zotero#3287 --- src/pdf/lib/coordinates.js | 2 +- src/pdf/lib/render.js | 62 +++++++++++++++++++++++++++++++------- src/pdf/page.js | 32 +++++++++++--------- 3 files changed, 70 insertions(+), 26 deletions(-) diff --git a/src/pdf/lib/coordinates.js b/src/pdf/lib/coordinates.js index 1eccc448..3d60b8be 100644 --- a/src/pdf/lib/coordinates.js +++ b/src/pdf/lib/coordinates.js @@ -16,7 +16,7 @@ export function p2v(position, viewport) { // For text annotations if (position.fontSize) { - position2.fontSize = viewport.convertToViewportPoint(position.fontSize, 0)[0]; + position2.fontSize = position.fontSize * viewport.scale; } if (position.rotation) { position2.rotation = position.rotation; diff --git a/src/pdf/lib/render.js b/src/pdf/lib/render.js index 5fb3a11d..2f3dbf4e 100644 --- a/src/pdf/lib/render.js +++ b/src/pdf/lib/render.js @@ -1,5 +1,9 @@ import { p2v } from './coordinates'; -import { getRotationTransform } from './utilities'; +import { + applyInverseTransform, + applyTransform, + transform +} from './utilities'; function calculateLines(context, text, maxWidth) { let words = text.split(' '); @@ -111,20 +115,56 @@ export function drawAnnotationsOnCanvas(canvas, viewport, annotations) { ctx.stroke(); } else if (annotation.type === 'text') { - let fontSize = position.fontSize; - let lineHeight = fontSize * 1.2; // 1.2 is a common line height for many fonts + let position = annotation.position; let rect = position.rects[0]; - let x = rect[0]; - let y = rect[3] - lineHeight; - let width = rect[2] - rect[0] + 10; + let width = rect[2] - rect[0]; + let lineHeight = position.fontSize * 1.2; // 1.2 is a common line height for many fonts ctx.fillStyle = annotation.color; - ctx.font = fontSize + 'px ' + window.computedFontFamily; + ctx.font = position.fontSize + 'px ' + window.computedFontFamily; + + // Translation matrix where the drawing starts + let x = rect[0]; + let y = rect[1]; + let translatedViewportMatrix = transform(viewport.transform, [1, 0, 0, 1, x, y]); + + // Rotation matrix + let degrees = -position.rotation * Math.PI / 180; + let cosValue = Math.cos(degrees); + let sinValue = Math.sin(degrees); + let rotationMatrix = [cosValue, sinValue, -sinValue, cosValue, 0, 0]; + + // Flip matrix because text gets inverted otherwise + let flipMatrix = [1, 0, 0, -1, 0, 0]; + // Combine flip and rotation matrices + let flipAndRotationMatrix = transform(flipMatrix, rotationMatrix); + + // Annotation center without x and y coordinates because we translate the viewport + let centerX = (rect[2] - rect[0]) / 2; + let centerY = (rect[3] - rect[1]) / 2; + + // Calculate center in the viewport matrix + let [x1, y1] = applyTransform([centerX, centerY], translatedViewportMatrix); + let [x2, y2] = applyTransform([centerX, centerY], transform(translatedViewportMatrix, flipAndRotationMatrix)); + + // Annotation center drift after applying flip and rotation matrix + let deltaX = x1 - x2; + let deltaY = y1 - y2; + + // Adjust delta x and y scale and sign + let viewportWithoutTranslationMatrix = viewport.transform.slice(); + viewportWithoutTranslationMatrix[4] = viewportWithoutTranslationMatrix[5] = 0; + [deltaX, deltaY] = applyInverseTransform([deltaX, deltaY], viewportWithoutTranslationMatrix); + // Correct flipAndRotationMatrix to have rotation and flip around the annotation center + flipAndRotationMatrix[4] = deltaX; + flipAndRotationMatrix[5] = deltaY; + + let finalMatrix = transform(translatedViewportMatrix, flipAndRotationMatrix); + ctx.transform(...finalMatrix); + let lineIndex = 0; let lines = calculateLines(ctx, annotation.comment, width); - let tm = getRotationTransform(rect, -position.rotation); - ctx.transform(...tm); for (let line of lines) { - ctx.fillText(line, x, y); - y += lineHeight; + ctx.fillText(line, 0, lineIndex * lineHeight + lineHeight); + lineIndex++; } } ctx.restore(); diff --git a/src/pdf/page.js b/src/pdf/page.js index 80c80a56..d5570714 100644 --- a/src/pdf/page.js +++ b/src/pdf/page.js @@ -6,7 +6,8 @@ import { transform, scaleShape, getRotationDegrees, - normalizeDegrees + normalizeDegrees, + inverseTransform } from './lib/utilities'; import { DARKEN_INK_AND_TEXT_COLOR, MIN_IMAGE_ANNOTATION_SIZE, SELECTION_COLOR } from '../common/defines'; import { getRectRotationOnText } from './selection'; @@ -382,21 +383,26 @@ export default class Page { for (let annotation of annotations) { if (annotation.type === 'text' && annotation.position.pageIndex === this.pageIndex) { - let position = annotation.position; - - if (action && ['resize', 'rotate'].includes(action.type) && action.annotation.id === annotation.id) { + let rect = position.rects[0]; + if (action && action.position && ['resize', 'rotate'].includes(action.type) && action.annotation.id === annotation.id) { position = action.position; } let node = customAnnotations.find(x => x.getAttribute('data-id') === annotation.id); - let disabled = this.layer._readOnly || annotation.readOnly; - - let top = this.originalPage.viewport.viewBox[3] - position.rects[0][3]; - let left = position.rects[0][0]; - let width = position.rects[0][2] - position.rects[0][0]; - let height = position.rects[0][3] - position.rects[0][1]; + let viewport = this.originalPage.viewport; + let centerX = (rect[0] + rect[2]) / 2; + let centerY = (rect[1] + rect[3]) / 2; + // Exclude scale from viewport transform + let m = transform(inverseTransform([viewport.scale, 0, 0, viewport.scale, 0, 0]), viewport.transform); + let [x, y] = applyTransform([centerX, centerY], m); + let width = rect[2] - rect[0]; + let height = rect[3] - rect[1]; + let top = y - height / 2; + let left = x - width / 2; + + let rotation = viewport.rotation - (position.rotation || 0); let style = [ `left: calc(${left}px * var(--scale-factor))`, @@ -407,11 +413,9 @@ export default class Page { `height: calc(${height}px * var(--scale-factor))`, `color: ${darkenHex(annotation.color, DARKEN_INK_AND_TEXT_COLOR)}`, `font-size: calc(${position.fontSize}px * var(--scale-factor))`, - `font-family: ${window.computedFontFamily}` + `font-family: ${window.computedFontFamily}`, + `transform: rotate(${rotation}deg)` ]; - if (position.rotation) { - style.push(`transform: rotate(${-position.rotation}deg)`); - } style = style.join(';');