From de434fd83dd690660d2bd08f32f419f8da6ea476 Mon Sep 17 00:00:00 2001 From: Ralf Sternberg Date: Sat, 15 Apr 2023 00:34:15 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20text=20rise=20not=20always?= =?UTF-8?q?=20reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a text rise was set in the last chunk of a text content object, it was not reset at the beginning of the next text object. This caused the text rise to be applied to the rest of the page. Text rise is a text state operator [1] that is retained across text objects in a single content stream, i.e. a page. Consequently, this commit maintains the text state across text objects within a page. [1]: See 5.2 Text State Parameters and Operators https://archive.org/details/pdf1.7/page/n395/mode/1up --- CHANGELOG.md | 11 +++++++++-- examples/sample.js | 10 +--------- package-lock.json | 4 ++-- package.json | 2 +- src/layout.ts | 2 +- src/page.ts | 5 ++++- src/render-text.ts | 6 ++---- test/render-text.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 59 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3f0fb3..f450b5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog -## [0.4.0] - Unreleased +## [0.4.1] - Unreleased + +### Fixed + +* Text rise is now reset properly and does not affect subsequent text + elements anymore. + +## [0.4.0] - 2023-03-27 ### Breaking changes @@ -13,7 +20,7 @@ * Block attribute `verticalAlign` for vertical alignment of columns. * Attribute `lineDash` for graphics shapes. -## [0.3.3] - 2022-03-03 +## [0.3.3] - 2023-03-03 ### Fixed diff --git a/examples/sample.js b/examples/sample.js index 3075b13..46a5a2c 100644 --- a/examples/sample.js +++ b/examples/sample.js @@ -70,15 +70,7 @@ export default { { text: 'cillum dolore', color: 'red' }, ' eu fugiat nulla pariatur: ', { - text: [ - 'x', - { text: 'ⁿ⁻¹', rise: 3 }, - ' 10', - { text: '³', rise: 3 }, - ' H', - { text: '₂', rise: -3 }, - 'O', - ], + text: ['H', { text: '₂', rise: -3 }, 'O 10', { text: '⁻³', rise: 3 }], color: '#0066cc', }, ], diff --git a/package-lock.json b/package-lock.json index 75a86ca..e6ba176 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pdfmkr", - "version": "0.4.0", + "version": "0.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pdfmkr", - "version": "0.4.0", + "version": "0.4.1", "license": "MIT", "dependencies": { "@pdf-lib/fontkit": "^1.1.1", diff --git a/package.json b/package.json index fdf7a37..dca4692 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pdfmkr", - "version": "0.4.0", + "version": "0.4.1", "description": "Generate PDF documents and from JavaScript objects", "license": "MIT", "repository": { diff --git a/src/layout.ts b/src/layout.ts index 2eee970..1fdcb76 100644 --- a/src/layout.ts +++ b/src/layout.ts @@ -94,7 +94,7 @@ export function layoutPages(def: DocumentDefinition, doc: Document): Page[] { pages.push({ size: doc.pageSize, content: frame, header, footer }); } - //Re-layout headers and footers to provide them with the final page count. + // Re-layout headers and footers to provide them with the final page count. pages.forEach((page, idx) => { const pageInfo = { pageCount: pages.length, pageNumber: idx + 1, pageSize: doc.pageSize }; typeof def.header === 'function' && (page.header = layoutHeader(def.header(pageInfo), doc)); diff --git a/src/page.ts b/src/page.ts index 14c0a5f..e7e6d60 100644 --- a/src/page.ts +++ b/src/page.ts @@ -1,8 +1,10 @@ -import { PDFFont, PDFImage, PDFName, PDFPage } from 'pdf-lib'; +import { Color, PDFFont, PDFImage, PDFName, PDFPage } from 'pdf-lib'; import { Size } from './box.js'; import { Frame } from './layout.js'; +export type TextState = { color?: Color; font?: string; size?: number; rise?: number }; + export type Page = { size: Size; content: Frame; @@ -11,6 +13,7 @@ export type Page = { pdfPage?: PDFPage; fonts?: { [ref: string]: PDFName }; images?: { [ref: string]: PDFName }; + textState?: TextState; extGStates?: { [ref: string]: PDFName }; }; diff --git a/src/render-text.ts b/src/render-text.ts index 454cea2..da49cb0 100644 --- a/src/render-text.ts +++ b/src/render-text.ts @@ -15,11 +15,11 @@ import { import { Pos } from './box.js'; import { TextObject } from './layout.js'; -import { getPageFont, Page } from './page.js'; +import { getPageFont, Page, TextState } from './page.js'; export function renderText(object: TextObject, page: Page, base: Pos) { const contentStream: PDFContentStream = (page.pdfPage as any).getContentStream(); - const state: TextState = {}; + const state = (page.textState ??= {}); const x = base.x; const y = page.size.height - base.y; contentStream.push(beginText()); @@ -40,8 +40,6 @@ export function renderText(object: TextObject, page: Page, base: Pos) { contentStream.push(endText()); } -type TextState = { color?: Color; font?: string; size?: number; rise?: number }; - function setTextColorOp(state: TextState, color?: Color): PDFOperator | undefined { const effectiveColor = color ?? rgb(0, 0, 0); if (!equalsColor(state.color, effectiveColor)) { diff --git a/test/render-text.test.ts b/test/render-text.test.ts index 59e7a4b..8a7f488 100644 --- a/test/render-text.test.ts +++ b/test/render-text.test.ts @@ -63,6 +63,45 @@ describe('render-text', () => { ]); }); + it('maintains text state throughout page', () => { + const obj1: TextObject = { + type: 'text', + rows: [ + { + segments: [{ text: 'foo', font, fontSize: 10, rise: 3 }], + ...{ x: 1, y: 2, width: 60, height: 12, baseline: 8 }, + }, + ], + }; + const obj2: TextObject = { + type: 'text', + rows: [ + { + segments: [{ text: 'bar', font, fontSize: 10 }], + ...{ x: 3, y: 4, width: 60, height: 12, baseline: 8 }, + }, + ], + }; + + renderText(obj1, page, pos); + renderText(obj2, page, pos); + + expect(getContentStream(page)).toEqual([ + 'BT', + '1 0 0 1 11 770 Tm', + '0 0 0 rg', + '/fontA-1 10 Tf', + '3 Ts', + 'foo Tj', + 'ET', + 'BT', + '1 0 0 1 13 768 Tm', + '0 Ts', // reset text rise + 'bar Tj', + 'ET', + ]); + }); + it('renders multiple rows with multiple text segments', () => { const seg1 = { text: 'foo', font, fontSize: 10 }; const seg2 = { text: 'bar', font, fontSize: 10 };