diff --git a/apps/deno/tests/test1.ts b/apps/deno/tests/test1.ts index fe71ed5c7..4adc14984 100644 --- a/apps/deno/tests/test1.ts +++ b/apps/deno/tests/test1.ts @@ -23,6 +23,7 @@ import { setLineJoin, StandardFonts, AFRelationship, + TextRenderingMode, } from '../../../dist/pdf-lib.esm.js'; const ipsumLines = [ @@ -646,6 +647,50 @@ export default async (assets: Assets) => { form.removeField(textField); + /********************** Page 6 **********************/ + // This page tests different drawing operations as well as adding custom + // operators to the page content. + + const page6 = pdfDoc.addPage([size, size]); + + const text = 'These are the test words. '; + page6.drawText(text + 'regular', { + y: size - 20, + size: 20, + lineHeight: 20, + }); + page6.drawText(text + '+5 raised and fontsize 12 instead of 20', { + y: size - 20, + x: 325, + size: 12, + lineHeight: 20, + rise: 5, + }); + page6.drawText(text + '50% horizontal scale', { + y: size - 40, + size: 20, + lineHeight: 20, + horizontalScale: 50, + }); + page6.drawText(text + '+10 word space', { + y: size - 60, + size: 20, + lineHeight: 20, + wordSpace: 10, + }); + page6.drawText(text + '+4 character space', { + y: size - 80, + size: 20, + lineHeight: 20, + characterSpace: 4, + }); + page6.drawText(text + 'textRenderingMode = outline', { + y: size - 100, + size: 20, + lineHeight: 20, + renderingMode: TextRenderingMode.Outline, + }); + /********************** Print Metadata **********************/ console.log('Title:', pdfDoc.getTitle()); diff --git a/apps/node/tests/test1.ts b/apps/node/tests/test1.ts index 1fc9f74db..068362432 100644 --- a/apps/node/tests/test1.ts +++ b/apps/node/tests/test1.ts @@ -21,6 +21,7 @@ import { StandardFonts, typedArrayFor, AFRelationship, + TextRenderingMode, } from '../../..'; const ipsumLines = [ @@ -644,6 +645,50 @@ export default async (assets: Assets) => { form.removeField(textField); + /********************** Page 6 **********************/ + // This page tests different drawing operations as well as adding custom + // operators to the page content. + + const page6 = pdfDoc.addPage([size, size]); + + const text = 'These are the test words. '; + page6.drawText(text + 'regular', { + y: size - 20, + size: 20, + lineHeight: 20, + }); + page6.drawText(text + '+5 raised and fontsize 12 instead of 20', { + y: size - 20, + x: 325, + size: 12, + lineHeight: 20, + rise: 5, + }); + page6.drawText(text + '50% horizontal scale', { + y: size - 40, + size: 20, + lineHeight: 20, + horizontalScale: 50, + }); + page6.drawText(text + '+10 word space', { + y: size - 60, + size: 20, + lineHeight: 20, + wordSpace: 10, + }); + page6.drawText(text + '+4 character space', { + y: size - 80, + size: 20, + lineHeight: 20, + characterSpace: 4, + }); + page6.drawText(text + 'textRenderingMode = outline', { + y: size - 100, + size: 20, + lineHeight: 20, + renderingMode: TextRenderingMode.Outline, + }); + /********************** Print Metadata **********************/ console.log('Title:', pdfDoc.getTitle()); diff --git a/apps/rn/src/tests/test1.js b/apps/rn/src/tests/test1.js index e7ebd36b3..5ff110c0a 100644 --- a/apps/rn/src/tests/test1.js +++ b/apps/rn/src/tests/test1.js @@ -20,6 +20,7 @@ import { setLineJoin, StandardFonts, AFRelationship, + TextRenderingMode, } from 'pdf-lib'; import { fetchAsset } from './assets'; @@ -679,6 +680,50 @@ export default async () => { form.removeField(textField); + /********************** Page 6 **********************/ + // This page tests different drawing operations as well as adding custom + // operators to the page content. + + const page6 = pdfDoc.addPage([size, size]); + + const text = 'These are the test words. '; + page6.drawText(text + 'regular', { + y: size - 20, + size: 20, + lineHeight: 20, + }); + page6.drawText(text + '+5 raised and fontsize 12 instead of 20', { + y: size - 20, + x: 325, + size: 12, + lineHeight: 20, + rise: 5, + }); + page6.drawText(text + '50% horizontal scale', { + y: size - 40, + size: 20, + lineHeight: 20, + horizontalScale: 50, + }); + page6.drawText(text + '+10 word space', { + y: size - 60, + size: 20, + lineHeight: 20, + wordSpace: 10, + }); + page6.drawText(text + '+4 character space', { + y: size - 80, + size: 20, + lineHeight: 20, + characterSpace: 4, + }); + page6.drawText(text + 'textRenderingMode = outline', { + y: size - 100, + size: 20, + lineHeight: 20, + renderingMode: TextRenderingMode.Outline, + }); + /********************** Print Metadata **********************/ console.log('Title:', pdfDoc.getTitle()); diff --git a/apps/web/test1.html b/apps/web/test1.html index 5b881326d..30a28ef4c 100644 --- a/apps/web/test1.html +++ b/apps/web/test1.html @@ -79,6 +79,7 @@ rgb, StandardFonts, AFRelationship, + TextRenderingMode, } = PDFLib; const pdfDoc = await PDFDocument.create(); @@ -759,6 +760,50 @@ form.removeField(textField); + /********************** Page 6 **********************/ + // This page tests different drawing operations as well as adding custom + // operators to the page content. + + const page6 = pdfDoc.addPage([size, size]); + + const text = 'These are the test words. '; + page6.drawText(text + 'regular', { + y: size - 20, + size: 20, + lineHeight: 20, + }); + page6.drawText(text + '+5 raised and fontsize 12 instead of 20', { + y: size - 20, + x: 325, + size: 12, + lineHeight: 20, + rise: 5, + }); + page6.drawText(text + '50% horizontal scale', { + y: size - 40, + size: 20, + lineHeight: 20, + horizontalScale: 50, + }); + page6.drawText(text + '+10 word space', { + y: size - 60, + size: 20, + lineHeight: 20, + wordSpace: 10, + }); + page6.drawText(text + '+4 character space', { + y: size - 80, + size: 20, + lineHeight: 20, + characterSpace: 4, + }); + page6.drawText(text + 'textRenderingMode = outline', { + y: size - 100, + size: 20, + lineHeight: 20, + renderingMode: TextRenderingMode.Outline, + }); + /********************** Print Metadata **********************/ console.log('Title:', pdfDoc.getTitle()); diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index 7333440bd..558d544b9 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -14,6 +14,7 @@ import { translate, LineCapStyle, scale, + TextRenderingMode, } from 'src/api/operators'; import PDFDocument from 'src/api/PDFDocument'; import PDFEmbeddedPage from 'src/api/PDFEmbeddedPage'; @@ -976,6 +977,19 @@ export default class PDFPage { assertOrUndefined(options.lineHeight, 'options.lineHeight', ['number']); assertOrUndefined(options.maxWidth, 'options.maxWidth', ['number']); assertOrUndefined(options.wordBreaks, 'options.wordBreaks', [Array]); + assertOrUndefined(options.horizontalScale, 'options.horizontalScale', [ + 'number', + ]); + assertOrUndefined(options.wordSpace, 'options.wordSpace', ['number']); + assertOrUndefined(options.characterSpace, 'options.characterSpace', [ + 'number', + ]); + assertOrUndefined(options.rise, 'options.rise', ['number']); + assertIsOneOfOrUndefined( + options.renderingMode, + 'options.renderingMode', + TextRenderingMode, + ); assertIsOneOfOrUndefined(options.blendMode, 'options.blendMode', BlendMode); const { oldFont, newFont, newFontKey } = this.setOrEmbedFont(options.font); @@ -1010,7 +1024,13 @@ export default class PDFPage { x: options.x ?? this.x, y: options.y ?? this.y, lineHeight: options.lineHeight ?? this.lineHeight, + // optional: graphicsState: graphicsStateKey, + horizontalScale: options.horizontalScale, // defaults to 100 + wordSpace: options.wordSpace, // defaults to 0 + characterSpace: options.characterSpace, // defaults to 0 + rise: options.rise, // defaults to 0 + renderingMode: options.renderingMode, // defaults to TextRenderingMode.Fill }), ); diff --git a/src/api/PDFPageOptions.ts b/src/api/PDFPageOptions.ts index 6ddd8357f..73918409c 100644 --- a/src/api/PDFPageOptions.ts +++ b/src/api/PDFPageOptions.ts @@ -1,7 +1,7 @@ import { Color } from 'src/api/colors'; import PDFFont from 'src/api/PDFFont'; import { Rotation } from 'src/api/rotations'; -import { LineCapStyle } from 'src/api/operators'; +import { LineCapStyle, TextRenderingMode } from 'src/api/operators'; export enum BlendMode { Normal = 'Normal', @@ -32,6 +32,11 @@ export interface PDFPageDrawTextOptions { lineHeight?: number; maxWidth?: number; wordBreaks?: string[]; + horizontalScale?: number; + wordSpace?: number; + characterSpace?: number; + rise?: number; + renderingMode?: TextRenderingMode; } export interface PDFPageDrawImageOptions { diff --git a/src/api/operations.ts b/src/api/operations.ts index 0e46e6fdf..155b601b3 100644 --- a/src/api/operations.ts +++ b/src/api/operations.ts @@ -31,6 +31,12 @@ import { clip, endPath, appendBezierCurve, + TextRenderingMode, + setCharacterSpacing, + setHorizontalScaling, + setTextRenderingMode, + setTextRise, + setWordSpacing, } from 'src/api/operators'; import { Rotation, degrees, toRadians } from 'src/api/rotations'; import { svgPathToOperators } from 'src/api/svgPath'; @@ -47,6 +53,11 @@ export interface DrawTextOptions { x: number | PDFNumber; y: number | PDFNumber; graphicsState?: string | PDFName; + horizontalScale?: number | PDFNumber; + wordSpace?: number | PDFNumber; + characterSpace?: number | PDFNumber; + rise?: number | PDFNumber; + renderingMode?: TextRenderingMode; } export const drawText = ( @@ -66,6 +77,11 @@ export const drawText = ( options.x, options.y, ), + options.horizontalScale && setHorizontalScaling(options.horizontalScale), + options.wordSpace && setWordSpacing(options.wordSpace), + options.characterSpace && setCharacterSpacing(options.characterSpace), + options.rise && setTextRise(options.rise), + options.renderingMode && setTextRenderingMode(options.renderingMode), showText(line), endText(), popGraphicsState(), @@ -86,6 +102,11 @@ export const drawLinesOfText = ( setFillingColor(options.color), setFontAndSize(options.font, options.size), setLineHeight(options.lineHeight), + options.horizontalScale && setHorizontalScaling(options.horizontalScale), + options.wordSpace && setWordSpacing(options.wordSpace), + options.characterSpace && setCharacterSpacing(options.characterSpace), + options.rise && setTextRise(options.rise), + options.renderingMode && setTextRenderingMode(options.renderingMode), rotateAndSkewTextRadiansAndTranslate( toRadians(options.rotate), toRadians(options.xSkew), diff --git a/src/api/operators.ts b/src/api/operators.ts index 62b5e3a24..141b576c4 100644 --- a/src/api/operators.ts +++ b/src/api/operators.ts @@ -213,16 +213,28 @@ export const setFontAndSize = ( size: number | PDFNumber, ) => PDFOperator.of(Ops.SetFontAndSize, [asPDFName(name), asPDFNumber(size)]); +/** @param characterSpace extra space between characters; in unscaled text sapce units; can also be negative */ export const setCharacterSpacing = (spacing: number | PDFNumber) => PDFOperator.of(Ops.SetCharacterSpacing, [asPDFNumber(spacing)]); +/** @param wordSpace space "between words" (actually extra space for every ASCII SPACE character); in unscaled text sapce units; can also be negative; NOTE: This only works for fonts that define code 32 (=space) as a single-byte code, according to the standard. (E.g. it works with Helvetica, but not with ubuntuFont.) */ export const setWordSpacing = (spacing: number | PDFNumber) => PDFOperator.of(Ops.SetWordSpacing, [asPDFNumber(spacing)]); -/** @param squeeze horizontal character spacing */ +/** + * DEPRECATED: use "setHorizontalScaling" instead + * + * @param squeeze horizontal character spacing + */ export const setCharacterSqueeze = (squeeze: number | PDFNumber) => PDFOperator.of(Ops.SetTextHorizontalScaling, [asPDFNumber(squeeze)]); +/** @param horizontalScaling horizontal character spacing; value in %; default = 100 */ +export const setHorizontalScaling = (horizontalScaling: number | PDFNumber) => + PDFOperator.of(Ops.SetTextHorizontalScaling, [ + asPDFNumber(horizontalScaling), + ]); + export const setLineHeight = (lineHeight: number | PDFNumber) => PDFOperator.of(Ops.SetTextLineHeight, [asPDFNumber(lineHeight)]);