Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions base/display/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -1131,16 +1131,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// Render Type3 text within the transformation context
if (type3Text) {
info(`render Type3 text: '${type3Text}', disableFontFace: ${font.disableFontFace}`);
var curFontSize = fontSize;
var viewportScaleT3 = this.baseTransform
? Math.sqrt(this.baseTransform[0] * this.baseTransform[0] +
this.baseTransform[1] * this.baseTransform[1])
: 1.0;
var curFontSize = fontSize * viewportScaleT3;
var renderedHeight = curFontSize;
switch (current.textRenderingMode) {
case TextRenderingMode.FILL:
ctx.fillText(type3Text, 0, 0, canvasWidth, curFontSize);
ctx.fillText(type3Text, 0, 0, canvasWidth * viewportScaleT3, curFontSize, renderedHeight);
break;
case TextRenderingMode.STROKE:
ctx.strokeText(type3Text, 0, 0, canvasWidth, curFontSize);
ctx.strokeText(type3Text, 0, 0, canvasWidth * viewportScaleT3, curFontSize, renderedHeight);
break;
case TextRenderingMode.FILL_STROKE:
ctx.fillText(type3Text, 0, 0, canvasWidth, curFontSize);
ctx.fillText(type3Text, 0, 0, canvasWidth * viewportScaleT3, curFontSize, renderedHeight);
break;
case TextRenderingMode.INVISIBLE:
case TextRenderingMode.ADD_TO_PATH:
Expand Down Expand Up @@ -1170,7 +1175,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {

var lineWidth = current.lineWidth;
var a1 = current.textMatrix[0], b1 = current.textMatrix[1];
var c1 = current.textMatrix[2], d1 = current.textMatrix[3];
var scale = Math.sqrt(a1 * a1 + b1 * b1);
var scaleY = Math.sqrt(c1 * c1 + d1 * d1);
if (scale === 0 || lineWidth === 0)
lineWidth = this.getSinglePixelWidth();
else
Expand Down Expand Up @@ -1275,16 +1282,24 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {

// Text rendering for regular fonts (Type3 fonts are handled in their own context above)
if (str && !font.disableFontFace && !font.coded) {
var curFontSize = fontSize * scale * textHScale + 3;
// Scale from PDF text-space to viewport pixels so that rw/rh are
// in the same coordinate space as x/y (which come from getCoords_).
var viewportScale = this.baseTransform
? Math.sqrt(this.baseTransform[0] * this.baseTransform[0] +
this.baseTransform[1] * this.baseTransform[1])
: 1.0;
var curFontSize = fontSize * scale * textHScale * viewportScale + 3;
var renderedHeight = fontSize * scaleY * viewportScale + 3;
var scaledCanvasWidth = canvasWidth * viewportScale;
switch (current.textRenderingMode) {
case TextRenderingMode.FILL:
ctx.fillText(str, 0, 0, canvasWidth, curFontSize);
ctx.fillText(str, 0, 0, scaledCanvasWidth, curFontSize, renderedHeight);
break;
case TextRenderingMode.STROKE:
ctx.strokeText(str, 0, 0, canvasWidth, curFontSize);
ctx.strokeText(str, 0, 0, scaledCanvasWidth, curFontSize, renderedHeight);
break;
case TextRenderingMode.FILL_STROKE:
ctx.fillText(str, 0, 0, canvasWidth, curFontSize);
ctx.fillText(str, 0, 0, scaledCanvasWidth, curFontSize, renderedHeight);
break;
case TextRenderingMode.INVISIBLE:
case TextRenderingMode.ADD_TO_PATH:
Expand Down Expand Up @@ -2003,4 +2018,3 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {

return CanvasGraphics;
})();

7 changes: 4 additions & 3 deletions lib/pdfcanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export default class CanvasRenderingContext2D_ {
return this.dashArray;
}

fillText(text, x, y, maxWidth, fontSize) {
fillText(text, x, y, maxWidth, fontSize, renderedHeight) {
if (!text || (!text.length === 1 && text.trim().length < 1)) return;
const p = this.getCoords_(x, y);

Expand All @@ -260,14 +260,15 @@ export default class CanvasRenderingContext2D_ {
maxWidth,
color,
fontSize,
renderedHeight,
this.canvas,
this.m_
);
}

strokeText(text, x, y, maxWidth) {
strokeText(text, x, y, maxWidth, fontSize, renderedHeight) {
//MQZ. 10/23/2012, yeah, no hollow text for now
this.fillText(text, x, y, maxWidth);
this.fillText(text, x, y, maxWidth, fontSize, renderedHeight);
}

measureText(text) {
Expand Down
16 changes: 14 additions & 2 deletions lib/pdffont.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,11 @@ export default class PDFFont {
* @param {number} maxWidth - Maximum width
* @param {string} color - Color value
* @param {number} fontSize - Font size
* @param {number} renderedHeight - Rendered text height in pixels
* @param {{Texts: Array<any>}} targetData - Target data object
* @param {number[][]} matrix2D - 2D transformation matrix
*/
processText(p, str, maxWidth, color, fontSize, targetData, matrix2D) {
processText(p, str, maxWidth, color, fontSize, renderedHeight, targetData, matrix2D) {
// Debug the incoming text processing
PJS.info(`Processing text: '${str}', font type: ${this.fontObj.type || 'unknown'}, char code: ${str ? str.charCodeAt(0) : 'none'}`);

Expand Down Expand Up @@ -541,18 +542,29 @@ export default class PDFFont {
// Add RA property safely
textRun = Object.assign({}, textRun, { RA: rAngle });
}
const textHeight = Number.isFinite(renderedHeight) ? renderedHeight : fontSize;

// Calculate horizontal scale factor from fontSize (squeezed) vs renderedHeight (not squeezed)
// The fontSize parameter is curFontSize = original_fontSize * scale * textHScale + 3
// The renderedHeight parameter is original_fontSize * scaleY + 3
// So: scale factor = (fontSize - 3) / (renderedHeight - 3)
const denominator = renderedHeight - 3;
const horizontalScale = Math.abs(denominator) > 0.001 ? (fontSize - 3) / denominator : 1.0;
const renderedWidth = maxWidth * horizontalScale;

const oneText = {
x: PDFUnit.toFormX(p.x) - 0.25,
y: PDFUnit.toFormY(p.y) - 0.75,
w: PDFUnit.toFixedFloat(maxWidth),
rw: PDFUnit.toFormX(renderedWidth),
rh: PDFUnit.toFormY(textHeight),
...colorObj, //MQZ.07/29/2013: when color is not in color dictionary, set the original color (oc)
sw: this.spaceWidth, //font space width, use to merge adjacent text blocks
A: 'left',
R: [textRun],
// TT: this.fontObj.isSymbolicFont || this.fontObj.type === 'Type3' ? 1 : 0, // Add TT flag for symbolic and Type3 fonts
};

PJS.info(`Adding text to output: '${text}'`);
targetData.Texts.push(oneText);
}
Expand Down
5 changes: 4 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,10 @@ Each page object within 'Pages' array describes page elements and attributes wit
- v0.4.4 added dashed line support. Default is 'solid', if line style is dashed line, {dsh:1} is added to line object;
- 'Fills': an array of rectangular area with solid color fills, same as lines, each 'fill' object has 'x', 'y' in relative coordinates for positioning, 'w' and 'h' for width and height in page unit, plus 'clr' to reference a color with index in color dictionary. More info about 'color dictionary' can be found at 'Dictionary Reference' section.
- 'Texts': an array of text blocks with position, actual text and styling information:
- 'x' and 'y': relative coordinates for positioning
- 'x' and 'y': relative coordinates for positioning (in page units)
- 'w': logical character width in page units (before any text squeezing/transformation)
- 'rw': rendered width in page units (actual visual width accounting for horizontal text scaling/squeezing)
- 'rh': rendered height in page units (vertical metric, accounts for text transformation matrix)
- 'clr': a color index in color dictionary, same 'clr' field as in 'Fill' object. If a color can't be found in color dictionary, 'oc' field will be added to the field as 'original color" value.
- 'A': text alignment, including:
- left
Expand Down
2 changes: 2 additions & 0 deletions src/types/pdfparser.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ export declare interface Text {
x: number;
y: number;
w: number;
rw: number;
rh: number;
sw: number;
A: 'left' | 'center' | 'right';
R: TextRun[];
Expand Down
Binary file added test/pdf/h/hidden-action-test.pdf
Binary file not shown.
51 changes: 51 additions & 0 deletions test/test-hidden-field.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env node

const PDFParser = require('../dist/pdfparser.cjs');
const path = require('node:path');

const pdfPath = path.resolve(__dirname, 'pdf', 'h', 'hidden-action-test.pdf');

const pdfParser = new PDFParser();

pdfParser.on('pdfParser_dataError', (err) => {
console.error('[PDF Error]', err);
process.exit(1);
});

pdfParser.on('pdfParser_dataReady', (pdfData) => {
console.log('\n========== PDF Parsed Successfully ==========\n');

pdfData.Pages.forEach((page, pageIndex) => {
console.log(`\n--- Page ${pageIndex + 1} ---`);

if (page.Texts && page.Texts.length > 0) {
console.log(`\nText Elements: ${page.Texts.length}`);
page.Texts.forEach((text, idx) => {
console.log(` [${idx}] x=${text.x}, y=${text.y}, w=${text.w}, rw=${text.rw}, rh=${text.rh}`);
});
}

if (page.Fields && page.Fields.length > 0) {
console.log(`\nFields: ${page.Fields.length}`);
page.Fields.forEach((field, idx) => {
console.log(` [${idx}] id=${field.id.Id}, type=${field.T?.Name}, w=${field.w}, h=${field.h}, x=${field.x}, y=${field.y}`);
});
}

if (page.Boxsets && page.Boxsets.length > 0) {
console.log(`\nBoxsets: ${page.Boxsets.length}`);
page.Boxsets.forEach((boxset, idx) => {
console.log(` [${idx}] id=${boxset.id?.Id}`);
if (boxset.boxes) {
boxset.boxes.forEach((box, bidx) => {
console.log(` Box [${bidx}]: w=${box.w}, h=${box.h}, x=${box.x}, y=${box.y}`);
});
}
});
}
});

process.exit(0);
});

pdfParser.loadPDF(pdfPath);