Skip to content
Merged
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
67 changes: 66 additions & 1 deletion base/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,19 +506,84 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var fontResources = font.get('Resources') || resources;
var charProcKeys = Object.keys(charProcs);
var charProcOperatorList = {};

info(`Processing Type3 font: ${fontName}, found ${charProcKeys.length} CharProcs`);

// Create a mapping from character code to glyph name
var charProcMapping = {};
var encoding = font.get('Encoding');

if (encoding) {
info(`Type3 font has encoding: ${encoding.name || 'custom'}`);
var differences = encoding.get('Differences');
var baseEncoding = encoding.get('BaseEncoding');

// Process Differences array if it exists
if (differences) {
info(`Processing Differences array of length ${differences.length}`);
var currentCode = 0;
for (var i = 0; i < differences.length; i++) {
var entry = differences[i];
if (typeof entry === 'number') {
currentCode = entry;
info(`Setting current code to ${currentCode}`);
} else {
// Check the type of entry to debug what's happening
var entryType = typeof entry;
var entryValue;

// Ensure we always get a string name (not an object)
if (entryType === 'object' && entry.name) {
entryValue = entry.name;
} else if (entryType === 'object') {
entryValue = JSON.stringify(entry);
info(`Warning: Non-name object in Differences array: ${entryValue}`);
} else {
entryValue = entry.toString();
}

// info(`Entry type: ${entryType}, value: ${entryValue}`);

charProcMapping[currentCode] = entryValue;
// info(`Mapped code ${currentCode} to glyph '${entryValue}'`);
currentCode++;
}
}
}
// Use BaseEncoding if available
if (baseEncoding && baseEncoding.name) {
info(`Using BaseEncoding: ${baseEncoding.name}`);
var baseEncodingMap = Encodings[baseEncoding.name];
if (baseEncodingMap) {
for (var code = 0; code < 256; code++) {
if (!charProcMapping[code] && baseEncodingMap[code]) {
charProcMapping[code] = baseEncodingMap[code];
// info(`Mapped code ${code} to glyph '${baseEncodingMap[code]}' from BaseEncoding`);
}
}
}
}
}

// Store the mapping in the font object for text extraction
font.translated.charProcMapping = charProcMapping;
// info(`Final charProcMapping has ${Object.keys(charProcMapping).length} entries`);

for (var i = 0, n = charProcKeys.length; i < n; ++i) {
var key = charProcKeys[i];
var glyphStream = charProcs[key];
var operatorList = this.getOperatorList(glyphStream, fontResources);
charProcOperatorList[key] = operatorList.getIR();
// info(`Processed CharProc for glyph '${key}'`);
if (!parentOperatorList) {
continue;
}
// Add the dependencies to the parent operator list so they are
// resolved before sub operator list is executed synchronously.
parentOperatorList.addDependencies(charProcOperatorList.dependencies);
parentOperatorList.addDependencies(operatorList.dependencies);
}
font.translated.charProcOperatorList = charProcOperatorList;
font.translated.charProcMapping = charProcMapping;
font.loaded = true;
} else {
font.loaded = true;
Expand Down
75 changes: 70 additions & 5 deletions base/core/fonts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2185,11 +2185,40 @@ var Font = (function FontClosure() {
this.cmap = properties.cmap;

this.fontMatrix = properties.fontMatrix;
if (properties.type == 'Type3') {
this.encoding = properties.baseEncoding;
return;
if (properties.type == 'Type3') {
this.encoding = properties.baseEncoding;
this.disableFontFace = true;
this.loadedName = this.loadedName || 'Type3Font';

// Add ability to map Type3 font glyphs to Unicode characters
if (properties.toUnicode) {
this.toUnicode = properties.toUnicode;
} else {
// Create a basic toUnicode map for common glyph names
const toUnicode = {};
const encoding = properties.baseEncoding || [];
for (let i = 0; i < encoding.length; i++) {
const glyphName = encoding[i];
if (glyphName && GlyphsUnicode[glyphName]) {
toUnicode[i] = String.fromCharCode(GlyphsUnicode[glyphName]);
}
}

// If there are differences, apply them too
if (properties.differences && properties.differences.length) {
for (let i = 0; i < 256; i++) {
if (properties.differences[i]) {
const glyphName = properties.differences[i];
if (typeof glyphName === 'string' && GlyphsUnicode[glyphName]) {
toUnicode[i] = String.fromCharCode(GlyphsUnicode[glyphName]);
}
}
}
}
this.toUnicode = toUnicode;
}
return;
}

// Trying to fix encoding using glyph CIDSystemInfo.
this.loadCidToUnicode(properties);
this.cidEncoding = properties.cidEncoding;
Expand Down Expand Up @@ -4494,7 +4523,43 @@ var Font = (function FontClosure() {
case 'Type3':
var glyphName = this.differences[charcode] || this.encoding[charcode];
operatorList = this.charProcOperatorList[glyphName];
fontCharCode = charcode;

// For text extraction, map the glyph name to Unicode if possible
if (glyphName) {
fontCharCode = GlyphsUnicode[glyphName] || charcode;

// Handle common symbolic glyphs
if (fontCharCode === charcode && typeof glyphName === 'string') {
// Special handling for specific glyphs
if (glyphName.startsWith('uni')) {
// Handle uniXXXX format
const hex = glyphName.substring(3);
if (/^[0-9A-F]{4,6}$/i.test(hex)) {
fontCharCode = parseInt(hex, 16);
}
}

// Check if it's a common symbol
const commonSymbols = {
'bullet': 0x2022,
'checkbox': 0x2610,
'checkmark': 0x2713,
'circle': 0x25CB,
'square': 0x25A1,
'triangle': 0x25B2,
'triangledown': 0x25BC,
'triangleleft': 0x25C0,
'triangleright': 0x25B6,
'star': 0x2605
};

if (commonSymbols[glyphName.toLowerCase()]) {
fontCharCode = commonSymbols[glyphName.toLowerCase()];
}
}
} else {
fontCharCode = charcode;
}
break;
case 'TrueType':
if (this.useToFontChar) {
Expand Down
121 changes: 82 additions & 39 deletions base/display/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -903,8 +903,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.current.fontSize = size;

if (fontObj.coded) {
warn('Unsupported Type3 font (custom Glyph) - ' + fontRefName);
return; // we don't need ctx.font for Type3 fonts
warn('Found Type3 font (custom Glyph) - ' + fontRefName + ', trying to decode'); // MQZ 8/23 added Type3 glyph font support
// MQZ. 08/24/2025 need to set up the font context for glyph based text processing
this.ctx.setFont(fontObj);
return; // we don't need ctx.font for Type3 fonts
}

var name = fontObj.loadedName || 'sans-serif';
Expand Down Expand Up @@ -1053,13 +1055,36 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var glyphsLength = glyphs.length;
var textLayer = this.textLayer;
var geom;
var textSelection = textLayer && !skipTextSelection ? true : false;

// Always use textSelection for Type3 fonts
var textSelection = textLayer && (font.coded || !skipTextSelection) ? true : false;
var type3Text = "";

var canvasWidth = 0.0;
var vertical = font.vertical;
var defaultVMetrics = font.defaultVMetrics;

info(`showText called with ${glyphsLength} glyphs, font type: ${font.coded ? 'Type3' : font.type || 'Unknown'}, textSelection: ${textSelection}`);

// Type3 fonts - each glyph is a "mini-PDF"
if (font.coded) {
info(`Processing Type3 font with ${glyphsLength} glyphs`);

// For Type3 fonts, collect unicode characters or character codes
for (var i = 0; i < glyphsLength; ++i) {
var glyph = glyphs[i];
if (glyph !== null) {
// Use unicode value if available, otherwise use fontChar
if (glyph.unicode) {
type3Text += glyph.unicode;
} else if (glyph.fontChar) {
type3Text += String.fromCharCode(glyph.fontChar);
}
}
}
info(`Type3 text: ${type3Text}`);

// If we have collected text, store it for later use in appendText
ctx.save();
ctx.transform.apply(ctx, current.textMatrix);
ctx.translate(current.x, current.y);
Expand All @@ -1070,18 +1095,22 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.save();
ctx.scale(1, -1);
geom = this.createTextGeometry();
// Add the Type3 text to the geometry object so it can be added to the output
geom.type3Text = type3Text;
geom.fontSize = fontSize;
this.restore();
}
for (var i = 0; i < glyphsLength; ++i) {

var glyph = glyphs[i];
if (glyph === null) {
// word break
info(`Type3 word break at glyph ${i}`);
this.ctx.translate(wordSpacing, 0);
current.x += wordSpacing * textHScale;
continue;
}

//info(`Processing Type3 glyph ${i}: ${glyph.unicode || glyph.fontChar}`);
this.processingType3 = glyph;
this.save();
ctx.scale(fontSize, fontSize);
Expand All @@ -1093,24 +1122,46 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var width = (transformed[0] * fontSize + charSpacing) *
current.fontDirection;

//info(`Type3 glyph width: ${width}`);
ctx.translate(width, 0);
current.x += width * textHScale;

canvasWidth += width;
}
// Render Type3 text within the transformation context
if (type3Text) {
info(`render Type3 text: '${type3Text}', disableFontFace: ${font.disableFontFace}`);
var curFontSize = fontSize;
switch (current.textRenderingMode) {
case TextRenderingMode.FILL:
ctx.fillText(type3Text, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.STROKE:
ctx.strokeText(type3Text, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.FILL_STROKE:
ctx.fillText(type3Text, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.INVISIBLE:
case TextRenderingMode.ADD_TO_PATH:
break;
default: // other unsupported rendering modes
}
}

ctx.restore();
this.processingType3 = null;
} else {
ctx.save();

//MQZ Dec.04.2013 handles leading word spacing
var tx = 0;
if (wordSpacing !== 0) {
var firstGlyph = glyphs.filter(g => g && ('fontChar' in g || 'unicode' in g))[0];
if (firstGlyph && (firstGlyph.fontChar === ' ' || firstGlyph.unicode === ' ')) {
//MQZ Dec.04.2013 handles leading word spacing
var tx = 0;
if (wordSpacing !== 0) {
var firstGlyph = glyphs.filter(g => g && ('fontChar' in g || 'unicode' in g))[0];
if (firstGlyph && (firstGlyph.fontChar === ' ' || firstGlyph.unicode === ' ')) {
tx = wordSpacing * fontSize * textHScale;
}
}
}
}

current.x += tx
this.applyTextTransforms();
Expand All @@ -1135,8 +1186,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {

ctx.lineWidth = lineWidth;

//MQZ. Feb.20.2013. Disable character based painting, make it a string
var str = "";
//MQZ. Feb.20.2013. Disable character based painting, make it a string
var str = "";

var x = 0;
for (var i = 0; i < glyphsLength; ++i) {
Expand Down Expand Up @@ -1188,7 +1239,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {

//MQZ. Feb.20.2013. Disable character based painting, make it a string
// this.paintChar(character, scaledX, scaledY);
str += glyph.unicode || character;
str += glyph.unicode || character;
if (accent) {
scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
Expand Down Expand Up @@ -1218,35 +1269,28 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// info(nodeUtil.inspect(glyphs));
// }

if (str && !font.disableFontFace) {
var curFontSize = fontSize * scale * textHScale + 3;
switch (current.textRenderingMode) {
case TextRenderingMode.FILL:
ctx.fillText(str, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.STROKE:
ctx.strokeText(str, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.FILL_STROKE:
ctx.fillText(str, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.INVISIBLE:
case TextRenderingMode.ADD_TO_PATH:
break;
default: // other unsupported rendering modes
}
}

ctx.restore();
}

if (textSelection) {
geom.canvasWidth = canvasWidth;
if (vertical) {
var VERTICAL_TEXT_ROTATION = Math.PI / 2;
geom.angle += VERTICAL_TEXT_ROTATION;
// 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;
switch (current.textRenderingMode) {
case TextRenderingMode.FILL:
ctx.fillText(str, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.STROKE:
ctx.strokeText(str, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.FILL_STROKE:
ctx.fillText(str, 0, 0, canvasWidth, curFontSize);
break;
case TextRenderingMode.INVISIBLE:
case TextRenderingMode.ADD_TO_PATH:
break;
default: // other unsupported rendering modes
}
this.textLayer.appendText(geom);
}

return canvasWidth;
Expand Down Expand Up @@ -1334,7 +1378,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var VERTICAL_TEXT_ROTATION = Math.PI / 2;
geom.angle += VERTICAL_TEXT_ROTATION;
}
this.textLayer.appendText(geom);
}
},
nextLineShowText: function CanvasGraphics_nextLineShowText(text) {
Expand Down
Loading
Loading