diff --git a/lib/mixins/text.js b/lib/mixins/text.js index d8bc693c..ddd984b4 100644 --- a/lib/mixins/text.js +++ b/lib/mixins/text.js @@ -1,5 +1,6 @@ import LineWrapper from '../line_wrapper'; import PDFObject from '../object'; +import { cosine, sine } from '../utils'; const { number } = PDFObject; @@ -108,9 +109,7 @@ export default { ({ x, y } = this); const lineGap = options.lineGap ?? this._lineGap ?? 0; const lineHeight = this.currentLineHeight(true) + lineGap; - let contentWidth = 0, - contentHeight = 0; - + let contentWidth = 0; // Convert text to a string string = String(string ?? ''); @@ -123,7 +122,7 @@ export default { if (options.width) { let wrapper = new LineWrapper(this, options); wrapper.on('line', (text, options) => { - contentHeight += lineHeight; + this.y+=lineHeight; text = text.replace(/\n/g, ''); if (text.length) { @@ -161,11 +160,18 @@ export default { // render paragraphs as single lines for (let line of string.split('\n')) { const lineWidth = this.widthOfString(line, options); - contentHeight += lineHeight; + this.y += lineHeight; contentWidth = Math.max(contentWidth, lineWidth); } } + let contentHeight = this.y - y; + // Clamp height to max height + if (options.height) contentHeight = Math.min(contentHeight, options.height); + + this.x = x; + this.y = y; + /** * Rotates around top left corner * [x1,y1] > [x2,y2] @@ -200,9 +206,8 @@ export default { } // Non-trivial values so time for trig - const angleRad = (options.rotation * Math.PI) / 180; - const cos = Math.cos(angleRad); - const sin = Math.sin(angleRad); + const cos = cosine(options.rotation); + const sin = sine(options.rotation); const x1 = x; const y1 = y; diff --git a/lib/utils.js b/lib/utils.js index c243ebb4..f1e5bd5d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -96,3 +96,33 @@ export const CM_TO_IN = 1 / 2.54; // 1CM = 1/2.54 IN export const PX_TO_IN = 1 / 96; // 1 PX = 1/96 IN export const IN_TO_PT = 72; // 1 IN = 72 PT export const PC_TO_PT = 12; // 1 PC = 12 PT + +/** + * Get cosine in degrees of a + * + * Rounding errors are handled + * @param a + * @returns {number} + */ +export function cosine(a) { + if (a === 0) return 1; + if (a === 90) return 0; + if (a === 180) return -1; + if (a === 270) return 0; + return Math.cos(a * Math.PI / 180); +} + +/** + * Get sine in degrees of a + * + * Rounding errors are handled + * @param a + * @returns {number} + */ +export function sine(a) { + if (a === 0) return 0; + if (a === 90) return 1; + if (a === 180) return 0; + if (a === 270) return -1; + return Math.sin(a * Math.PI / 180); +} diff --git a/tests/visual/__image_snapshots__/text-spec-js-text-rotated-multi-line-text-1-snap.png b/tests/visual/__image_snapshots__/text-spec-js-text-rotated-multi-line-text-1-snap.png new file mode 100644 index 00000000..a95bfb72 Binary files /dev/null and b/tests/visual/__image_snapshots__/text-spec-js-text-rotated-multi-line-text-1-snap.png differ diff --git a/tests/visual/text.spec.js b/tests/visual/text.spec.js index f4941040..a835db65 100644 --- a/tests/visual/text.spec.js +++ b/tests/visual/text.spec.js @@ -142,4 +142,29 @@ describe('text', function() { doc.save().circle(200, 200, 1).fill('blue').restore(); }); }); + + test('rotated multi line text', function () { + return runDocTest(function (doc) { + doc.font('tests/fonts/Roboto-Regular.ttf'); + let text = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`; + + // Compute text rect + const textBounds = doc.boundsOfString(text, 300, 300, { rotation: 0 }); + // Compute rotated bounds + const bounds = doc.boundsOfString(text, 300, 300, { rotation: 45 }); + doc + // Draw rotated bounds + .rect(bounds.x, bounds.y, bounds.width, bounds.height) + .stroke() + // Draw text + .text(text, 300, 300, { rotation: 45 }) + // Draw text rect (rotation is negative because of the flipped axis + .rotate(-45, {origin: [300,300]}) + .rect(300, 300, textBounds.width, textBounds.height) + .stroke("pink") + // Draw origin + .circle(300, 300, 2) + .fill('blue') + }); + }); });