UNPKG

@syncfusion/ej2-pdf

Version:

Feature-rich JavaScript PDF library with built-in support for loading and manipulating PDF document.

530 lines (529 loc) 20 kB
/** * Arabic shaping engine that converts base Arabic code points into their * contextual presentation forms (isolated/initial/medial/final), handles * marks (shadda/hamza/madda/tanween), and common ligatures. * * @private */ var _ArabicShapeRenderer = /** @class */ (function () { /** * Creates an instance of the 'ArabicShapeRenderer' class. * * @private */ function _ArabicShapeRenderer() { var _this = this; /** * Arabic presentation forms for letters in isolated, initial, medial, and final shapes. * * @private */ this._arabicCharTable = [['\u0621', '\uFE80'], ['\u0622', '\uFE81', '\uFE82'], ['\u0623', '\uFE83', '\uFE84'], ['\u0624', '\uFE85', '\uFE86'], ['\u0625', '\uFE87', '\uFE88'], ['\u0626', '\uFE89', '\uFE8A', '\uFE8B', '\uFE8C'], ['\u0627', '\uFE8D', '\uFE8E'], ['\u0628', '\uFE8F', '\uFE90', '\uFE91', '\uFE92'], ['\u0629', '\uFE93', '\uFE94'], ['\u062A', '\uFE95', '\uFE96', '\uFE97', '\uFE98'], ['\u062B', '\uFE99', '\uFE9A', '\uFE9B', '\uFE9C'], ['\u062C', '\uFE9D', '\uFE9E', '\uFE9F', '\uFEA0'], ['\u062D', '\uFEA1', '\uFEA2', '\uFEA3', '\uFEA4'], ['\u062E', '\uFEA5', '\uFEA6', '\uFEA7', '\uFEA8'], ['\u062F', '\uFEA9', '\uFEAA'], ['\u0630', '\uFEAB', '\uFEAC'], ['\u0631', '\uFEAD', '\uFEAE'], ['\u0632', '\uFEAF', '\uFEB0'], ['\u0633', '\uFEB1', '\uFEB2', '\uFEB3', '\uFEB4'], ['\u0634', '\uFEB5', '\uFEB6', '\uFEB7', '\uFEB8'], ['\u0635', '\uFEB9', '\uFEBA', '\uFEBB', '\uFEBC'], ['\u0636', '\uFEBD', '\uFEBE', '\uFEBF', '\uFEC0'], ['\u0637', '\uFEC1', '\uFEC2', '\uFEC3', '\uFEC4'], ['\u0638', '\uFEC5', '\uFEC6', '\uFEC7', '\uFEC8'], ['\u0639', '\uFEC9', '\uFECA', '\uFECB', '\uFECC'], ['\u063A', '\uFECD', '\uFECE', '\uFECF', '\uFED0'], ['\u0640', '\u0640', '\u0640', '\u0640', '\u0640'], ['\u0641', '\uFED1', '\uFED2', '\uFED3', '\uFED4'], ['\u0642', '\uFED5', '\uFED6', '\uFED7', '\uFED8'], ['\u0643', '\uFED9', '\uFEDA', '\uFEDB', '\uFEDC'], ['\u0644', '\uFEDD', '\uFEDE', '\uFEDF', '\uFEE0'], ['\u0645', '\uFEE1', '\uFEE2', '\uFEE3', '\uFEE4'], ['\u0646', '\uFEE5', '\uFEE6', '\uFEE7', '\uFEE8'], ['\u0647', '\uFEE9', '\uFEEA', '\uFEEB', '\uFEEC'], ['\u0648', '\uFEED', '\uFEEE'], ['\u0649', '\uFEEF', '\uFEF0', '\uFBE8', '\uFBE9'], ['\u064A', '\uFEF1', '\uFEF2', '\uFEF3', '\uFEF4'], ['\u0671', '\uFB50', '\uFB51'], ['\u0679', '\uFB66', '\uFB67', '\uFB68', '\uFB69'], ['\u067A', '\uFB5E', '\uFB5F', '\uFB60', '\uFB61'], ['\u067B', '\uFB52', '\uFB53', '\uFB54', '\uFB55'], ['\u067E', '\uFB56', '\uFB57', '\uFB58', '\uFB59'], ['\u067F', '\uFB62', '\uFB63', '\uFB64', '\uFB65'], ['\u0680', '\uFB5A', '\uFB5B', '\uFB5C', '\uFB5D'], ['\u0683', '\uFB76', '\uFB77', '\uFB78', '\uFB79'], ['\u0684', '\uFB72', '\uFB73', '\uFB74', '\uFB75'], ['\u0686', '\uFB7A', '\uFB7B', '\uFB7C', '\uFB7D'], ['\u0687', '\uFB7E', '\uFB7F', '\uFB80', '\uFB81'], ['\u0688', '\uFB88', '\uFB89'], ['\u068C', '\uFB84', '\uFB85'], ['\u068D', '\uFB82', '\uFB83'], ['\u068E', '\uFB86', '\uFB87'], ['\u0691', '\uFB8C', '\uFB8D'], ['\u0698', '\uFB8A', '\uFB8B'], ['\u06A4', '\uFB6A', '\uFB6B', '\uFB6C', '\uFB6D'], ['\u06A6', '\uFB6E', '\uFB6F', '\uFB70', '\uFB71'], ['\u06A9', '\uFB8E', '\uFB8F', '\uFB90', '\uFB91'], ['\u06AD', '\uFBD3', '\uFBD4', '\uFBD5', '\uFBD6'], ['\u06AF', '\uFB92', '\uFB93', '\uFB94', '\uFB95'], ['\u06B1', '\uFB9A', '\uFB9B', '\uFB9C', '\uFB9D'], ['\u06B3', '\uFB96', '\uFB97', '\uFB98', '\uFB99'], ['\u06BA', '\uFB9E', '\uFB9F'], ['\u06BB', '\uFBA0', '\uFBA1', '\uFBA2', '\uFBA3'], ['\u06BE', '\uFBAA', '\uFBAB', '\uFBAC', '\uFBAD'], ['\u06C0', '\uFBA4', '\uFBA5'], ['\u06C1', '\uFBA6', '\uFBA7', '\uFBA8', '\uFBA9'], ['\u06C5', '\uFBE0', '\uFBE1'], ['\u06C6', '\uFBD9', '\uFBDA'], ['\u06C7', '\uFBD7', '\uFBD8'], ['\u06C8', '\uFBDB', '\uFBDC'], ['\u06C9', '\uFBE2', '\uFBE3'], ['\u06CB', '\uFBDE', '\uFBDF'], ['\u06CC', '\uFBFC', '\uFBFD', '\uFBFE', '\uFBFF'], ['\u06D0', '\uFBE4', '\uFBE5', '\uFBE6', '\uFBE7'], ['\u06D2', '\uFBAE', '\uFBAF'], ['\u06D3', '\uFBB0', '\uFBB1'] ]; /** * Arabic letter Alef. * * @private */ this._alef = '\u0627'; /** * Arabic letter Alef with Hamza above. * * @private */ this._alefHamza = '\u0623'; /** * Arabic letter Alef with Hamza below. * * @private */ this._alefHamzaBelow = '\u0625'; /** * Arabic letter Alef with Madda above. * * @private */ this._alefMadda = '\u0622'; /** * Arabic letter Lam. * * @private */ this._lam = '\u0644'; /** * Arabic letter Lam. * * @private */ this._hamza = '\u0621'; /** * Zero Width Joiner character used in shaping. * * @private */ this._zeroWidthJoiner = '\u200D'; /** * Arabic combining Hamza above mark. * * @private */ this._hamzaAbove = '\u0654'; /** * Arabic combining Hamza below mark. * * @private */ this._hamzaBelow = '\u0655'; /** * Arabic letter Waw with Hamza above. * * @private */ this._wawHamza = '\u0624'; /** * Arabic letter Yeh with Hamza above. * * @private */ this._yehHamza = '\u0626'; /** * Arabic letter waw. * * @private */ this._waw = '\u0648'; /** * Arabic letter Alef Maqsura Yeh without dots. * * @private */ this._alefsura = '\u0649'; /** * Arabic letter Yeh. * * @private */ this._yeh = '\u064A'; /** * Farsi Yeh character. * * @private */ this._farsiYeh = '\u06CC'; /** * Arabic Shadda combining mark. * * @private */ this._shadda = '\u0651'; /** * Arabic Madda combining mark. * * @private */ this._madda = '\u0653'; /** * Ligature LAM-ALEF with small alef above (isolated/final). * * @private */ this._lwa = '\uFEFB'; /** * Ligature LAM with WAW and Hamza above (isolated). * * @private */ this._lwawh = '\uFEF7'; /** * Ligature LAM with WAW and Hamza below (isolated). * * @private */ this._lwawhb = '\uFEF9'; /** * Ligature LAM-ALEF with Madda above (isolated). * * @private */ this._lwawm = '\uFEF5'; /** * Urdu Heh with Yeh above (ligature part). * * @private */ this._bwhb = '\u06D3'; /** * Arabic Fathatan diacritic. * * @private */ this._fathatan = '\u064B'; /** * Arabic superscript alef. * * @private */ this._superalef = '\u0670'; /** * Bitmask flag used to mark vowel/diacritic characters. * * @private */ this._vowel = 0x1; /** * Map of base Arabic characters to their contextual shape forms. * * @private */ this._arabicMapTable = new Map(); this._arabicCharTable.forEach(function (charTableEntry) { _this._arabicMapTable.set(charTableEntry[0], charTableEntry); }); } /** * Resolves the contextual presentation form for a given base Arabic character * by index 0 = isolated, 1 = final, 2 = initial, 3 = medial based on table layout. * Returns the input unchanged when outside the shaping range or when presentation * form is not applicable. * * @private * @param {string} input The base character to shape. * @param {number} index The contextual form index offset (added to table base). * @returns {string} The presentation form for the specified index, or the input if not applicable. */ _ArabicShapeRenderer.prototype._getCharacterShape = function (input, index) { if ((input >= this._hamza) && (input <= this._bwhb)) { var value = []; if (this._arabicMapTable.get(input)) { value = this._arabicMapTable.get(input); return value[index + 1]; } } else if (input >= this._lwawm && input <= this._lwa) { return (input); } return input; }; /** * Shapes a mixed text string by detecting Arabic spans, * running shaping on those spans, and concatenating with untouched non Arabic text. * * @private * @param {string} text The input text (may contain mixed scripts). * @returns {string} The shaped text with Arabic spans converted to presentation forms. */ _ArabicShapeRenderer.prototype._shape = function (text) { var builder = ''; var value = ''; for (var i = 0; i < text.length; i++) { var c = text[i]; if (c >= '؀' && c <= 'ۿ') { value = value + c; } else { if (value.length > 0) { var shapeText = this._doShape(value.toString(), 0); builder = builder + shapeText; value = ''; } builder = builder + c; } } if (value.length > 0) { var shapeText = this._doShape(value.toString(), 0); builder = builder + shapeText; } return builder.toString(); }; /** * Performs contextual shaping over a pure Arabic span: applies joining logic, * marks (shadda/hamza/madda/tanween), and ligatures, producing * properly ordered presentation forms. * * @private * @param {string} input The Arabic substring to shape. * @param {number} level Bitfield flags affecting shaping (e.g., `_vowel`). * @returns {string} The shaped Arabic substring. */ _ArabicShapeRenderer.prototype._doShape = function (input, level) { var str = ''; var ligature = 0; var len = 0; var i = 0; var next = ''; var previous = new _ArabicShape(); var present = new _ArabicShape(); while (i < input.length) { next = input[i++]; ligature = this._ligature(next, present); if (ligature === 0) { var shapeCount = this._getShapeCount(next); len = (shapeCount === 1) ? 0 : 2; if (previous._shapes > 2) { len += 1; } len = len % (present._shapes); present._shapeValue = this._getCharacterShape(present._shapeValue, len); str = this._append(str, previous, level); previous = present; present = new _ArabicShape(); present._shapeValue = next; present._shapes = shapeCount; present._shapeLigature++; } } len = (previous._shapes > 2) ? 1 : 0; len = len % (present._shapes); present._shapeValue = this._getCharacterShape(present._shapeValue, len); str = this._append(str, previous, level); str = this._append(str, present, level); return str.toString(); }; /** * Appends a shaped unit to the output builder in this order: * 1) base presentation form, 2) optional diacritic type, 3) optional vowel, * conditioned by the supplied `level` flags. * * @private * @param {string} builder The current output string. * @param {_ArabicShape} shape The shaped unit (base + marks) to append. * @param {number} level Bitfield flags controlling which components to emit. * @returns {string} The updated output string. */ _ArabicShapeRenderer.prototype._append = function (builder, shape, level) { if (shape._shapeValue !== '') { builder = builder + shape._shapeValue; shape._shapeLigature -= 1; if (shape._shapeType !== '') { if ((level & this._vowel) === 0) { builder = builder + shape._shapeType; shape._shapeLigature -= 1; } else { shape._shapeLigature -= 1; } } if (shape._shapeVowel !== '') { if ((level & this._vowel) === 0) { builder = builder + shape._shapeVowel; shape._shapeLigature -= 1; } else { shape._shapeLigature -= 1; } } } return builder; }; /** * Processes combining marks and ligatures for the current shape context: * - Combines shadda/hamza/madda/tanween into the current shape as type/vowel. * - Folds lam+alef variants to precomposed lam alef presentation forms. * * Returns: * 0 → no ligation/mark applied, * 1 → a mark consumed, * 2 → mark caused a base replacement, * 3 → lam alef ligature formed. * * @private * @param {string} value The next code point to attempt combining/ligation with. * @param {_ArabicShape} shape The current shape context. * @returns {number} The action code describing the change. */ _ArabicShapeRenderer.prototype._ligature = function (value, shape) { if (shape._shapeValue !== '') { var result = 0; if ((value >= this._fathatan && value <= this._hamzaBelow) || value === this._superalef) { result = 1; if ((shape._shapeVowel !== '') && (value !== this._shadda)) { result = 2; } if (value === this._shadda) { if (shape._shapeType === '') { shape._shapeType = this._shadda; } else { return 0; } } else if (value === this._hamzaBelow) { if (shape._shapeValue === this._alef) { shape._shapeValue = this._alefHamzaBelow; result = 2; } else if (shape._shapeValue === this._lwa) { shape._shapeValue = this._lwawhb; result = 2; } else { shape._shapeType = this._hamzaBelow; } } else if (value === this._hamzaAbove) { if (shape._shapeValue === this._alef) { shape._shapeValue = this._alefHamza; result = 2; } else if (shape._shapeValue === this._lwa) { shape._shapeValue = this._lwawh; result = 2; } else if (shape._shapeValue === this._waw) { shape._shapeValue = this._wawHamza; result = 2; } else if (shape._shapeValue === this._yeh || shape._shapeValue === this._alefsura || shape._shapeValue === this._farsiYeh) { shape._shapeValue = this._yehHamza; result = 2; } else { shape._shapeType = this._hamzaAbove; } } else if (value === this._madda) { if (shape._shapeValue === this._alef) { shape._shapeValue = this._alefMadda; result = 2; } } else { shape._shapeVowel = value; } if (result === 1) { shape._shapeLigature++; } return result; } if (shape._shapeVowel !== '') { return 0; } if (shape._shapeValue === this._lam) { if (value === this._alef) { shape._shapeValue = this._lwa; shape._shapes = 2; result = 3; } else if (value === this._alefHamza) { shape._shapeValue = this._lwawh; shape._shapes = 2; result = 3; } else if (value === this._alefHamzaBelow) { shape._shapeValue = this._lwawhb; shape._shapes = 2; result = 3; } else if (value === this._alefMadda) { shape._shapeValue = this._lwawm; shape._shapes = 2; result = 3; } } return result; } else { return 0; } }; /** * Returns the number of contextual presentation forms available for a character: * typically 1 (non joining), 2 (right joining/final/isolate), or 4 (dual joining), * and 4 for Zero Width Joiner logic. Defaults to 1 for non Arabic or marks. * * @private * @param {string} shape The character to evaluate. * @returns {number} The number of available contextual forms (1, 2, or 4). */ _ArabicShapeRenderer.prototype._getShapeCount = function (shape) { if ((shape >= this._hamza) && (shape <= this._bwhb) && !((shape >= this._fathatan && shape <= this._hamzaBelow) || shape === this._superalef)) { var c = []; if (this._arabicMapTable.get(shape)) { c = this._arabicMapTable.get(shape); return c.length - 1; } } else if (shape === this._zeroWidthJoiner) { return 4; } return 1; }; return _ArabicShapeRenderer; }()); export { _ArabicShapeRenderer }; /** * Mutable shaping state for a single Arabic unit, tracking the selected * presentation form, optional diacritic marks, ligature count, * and how many contextual forms the character supports. * * @private */ var _ArabicShape = /** @class */ (function () { function _ArabicShape() { /** * Holds the shaped character value for the current position. * * @private */ this._shapeValue = ''; /** * Describes the shaping type (isolated, initial, medial, final). * * @private */ this._shapeType = ''; /** * Stores any vowel or combining marks applied during shaping. * * @private */ this._shapeVowel = ''; /** * Indicates the ligature code or kind if applied. * * @private */ this._shapeLigature = 0; /** * Number of glyphs produced by shaping the current code point. * * @private */ this._shapes = 1; } return _ArabicShape; }()); export { _ArabicShape };