307 lines
8.8 KiB
JavaScript
307 lines
8.8 KiB
JavaScript
/*
|
|
* (c) Copyright Ascensio System SIA 2010-2024
|
|
*
|
|
* This program is a free software product. You can redistribute it and/or
|
|
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
* version 3 as published by the Free Software Foundation. In accordance with
|
|
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
* of any third-party rights.
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
*
|
|
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
* street, Riga, Latvia, EU, LV-1050.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of the Program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU AGPL version 3.
|
|
*
|
|
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
* grant you any rights under trademark law for use of our trademarks.
|
|
*
|
|
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
* well as technical writing content are licensed under the terms of the
|
|
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
*
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
(function(window)
|
|
{
|
|
const FONTSIZE = AscFonts.MEASURE_FONTSIZE || 576;
|
|
const STRING_MAX_LEN = AscFonts.GRAPHEME_STRING_MAX_LEN || 1024;
|
|
const COEF = AscFonts.GRAPHEME_COEF|| 25.4 / 72 / 64 / FONTSIZE;
|
|
const GRAPHEME_BUFFER = new Array(STRING_MAX_LEN);
|
|
let GRAPHEME_LEN = 0;
|
|
const GRAPHEMES_CACHE = {}; // FontId + [GID] -> GraphemeId
|
|
const GRAPHEMES = [[0, 0, 0, 0, 0, 0, 0, 0, [0x20]]]; // GraphemeId -> Grapheme
|
|
let GRAPHEME_INDEX = 0;
|
|
const NO_GRAPHEME = 0;
|
|
|
|
function InitGrapheme(nFontId, nFontStyle)
|
|
{
|
|
GRAPHEME_LEN = 3;
|
|
GRAPHEME_BUFFER[0] = nFontId << 8 | nFontStyle;
|
|
GRAPHEME_BUFFER[1] = 0;
|
|
GRAPHEME_BUFFER[2] = 0;
|
|
}
|
|
function AddGlyphToGrapheme(nGID, nAdvanceX, nAdvanceY, nOffsetX, nOffsetY)
|
|
{
|
|
GRAPHEME_BUFFER[GRAPHEME_LEN++] = nGID;
|
|
GRAPHEME_BUFFER[GRAPHEME_LEN++] = nAdvanceX;
|
|
GRAPHEME_BUFFER[GRAPHEME_LEN++] = nAdvanceY;
|
|
GRAPHEME_BUFFER[GRAPHEME_LEN++] = nOffsetX;
|
|
GRAPHEME_BUFFER[GRAPHEME_LEN++] = nOffsetY;
|
|
GRAPHEME_LEN++; // CodePoints
|
|
|
|
GRAPHEME_BUFFER[1] += nAdvanceX;
|
|
GRAPHEME_BUFFER[2] += 1;
|
|
}
|
|
function DrawGrapheme(nGraphemeId, oContext, nX, nY, nFontSize, coeff)
|
|
{
|
|
let oGrapheme = GRAPHEMES[nGraphemeId];
|
|
if (!oGrapheme)
|
|
return;
|
|
|
|
if (undefined === coeff)
|
|
coeff = 1;
|
|
|
|
let nFontId = oGrapheme[0] >> 8;
|
|
let nStyle = oGrapheme[0] & 0xF;
|
|
|
|
let sFontName = AscCommon.FontNameMap.GetName(nFontId);
|
|
oContext.SetFontInternal(sFontName, nFontSize, nStyle);
|
|
|
|
let nKoef = COEF * nFontSize * coeff;
|
|
if (1 === oGrapheme[2])
|
|
{
|
|
oContext.tg(oGrapheme[3], nX + oGrapheme[6] * nKoef, nY - oGrapheme[7] * nKoef, oGrapheme[8]);
|
|
}
|
|
else
|
|
{
|
|
let nKoef = COEF * nFontSize * coeff;
|
|
let nPos = 3;
|
|
for (let nIndex = 0, nCount = oGrapheme[2]; nIndex < nCount; ++nIndex)
|
|
{
|
|
let nGID = oGrapheme[nPos++];
|
|
let nAdvanceX = oGrapheme[nPos++];
|
|
let nAdvanceY = oGrapheme[nPos++];
|
|
let nOffsetX = oGrapheme[nPos++];
|
|
let nOffsetY = oGrapheme[nPos++];
|
|
let arrCodePoints = oGrapheme[nPos++];
|
|
|
|
oContext.tg(nGID, nX + nOffsetX * nKoef, nY - nOffsetY * nKoef, arrCodePoints);
|
|
nX += nAdvanceX * nKoef;
|
|
nY += nAdvanceY * nKoef;
|
|
}
|
|
}
|
|
}
|
|
function CompareGraphemes(g)
|
|
{
|
|
if (g.length !== GRAPHEME_LEN)
|
|
return false;
|
|
|
|
for (let nPos = 0; nPos < GRAPHEME_LEN; ++nPos)
|
|
{
|
|
if (g[nPos] !== GRAPHEME_BUFFER[nPos])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
function FillCodePoints(codePoints)
|
|
{
|
|
// Пишем по схеме: распределяем везде по 1, если есть лишние, то все они уходят в первый глиф, если
|
|
// последним глифам не хватает, тогда им проставляем пробелы
|
|
|
|
let nCount = GRAPHEME_BUFFER[2];
|
|
if (nCount <= 0)
|
|
return;
|
|
|
|
let nPos = 8;
|
|
let nCodePointPos = 0;
|
|
let nCodePointsCount = codePoints ? codePoints.getCount() : 0;
|
|
|
|
if (nCodePointPos >= nCodePointsCount)
|
|
{
|
|
GRAPHEME_BUFFER[nPos] = [0x20];
|
|
}
|
|
else
|
|
{
|
|
GRAPHEME_BUFFER[nPos] = [];
|
|
let nStoreCount = Math.max(1, nCodePointsCount - nCount + 1);
|
|
for (;nCodePointPos < nStoreCount; ++nCodePointPos)
|
|
{
|
|
GRAPHEME_BUFFER[nPos].push(codePoints.get(nCodePointPos));
|
|
}
|
|
}
|
|
|
|
nPos += 6;
|
|
for (let nIndex = 1; nIndex < nCount; ++nIndex, nPos += 6, nCodePointPos++)
|
|
{
|
|
if (nCodePointPos >= nCodePointsCount)
|
|
GRAPHEME_BUFFER[nPos] = [0x20];
|
|
else
|
|
GRAPHEME_BUFFER[nPos] = [codePoints.get(nCodePointPos)];
|
|
}
|
|
}
|
|
function GetGraphemeIndex(codePoints)
|
|
{
|
|
FillCodePoints(codePoints);
|
|
|
|
let arrBuffer = new Array(GRAPHEME_LEN);
|
|
for (let nIndex = 0; nIndex < GRAPHEME_LEN; ++nIndex)
|
|
arrBuffer[nIndex] = GRAPHEME_BUFFER[nIndex];
|
|
|
|
GRAPHEMES[++GRAPHEME_INDEX] = arrBuffer;
|
|
return GRAPHEME_INDEX;
|
|
}
|
|
function GetGrapheme(codePoints)
|
|
{
|
|
let nFontId = GRAPHEME_BUFFER[0];
|
|
if (!GRAPHEMES_CACHE[nFontId])
|
|
GRAPHEMES_CACHE[nFontId] = {};
|
|
|
|
let result = GRAPHEMES_CACHE[nFontId];
|
|
|
|
let nPos = 3;
|
|
while (nPos < GRAPHEME_LEN)
|
|
{
|
|
let nGID = GRAPHEME_BUFFER[nPos];
|
|
nPos += 6;
|
|
|
|
if (!result[nGID])
|
|
result[nGID] = {};
|
|
|
|
result = result[nGID];
|
|
}
|
|
|
|
// TODO: Для скорости проверку совпадения отключили (всегда совпадает)
|
|
if (!result.Grapheme)
|
|
result.Grapheme = GetGraphemeIndex(codePoints);
|
|
// else if (!CompareGraphemes(result.Buffer))
|
|
// return GetGraphemeIndex(codePoints);
|
|
|
|
return result.Grapheme;
|
|
}
|
|
function GetGraphemeWidth(nGraphemeId)
|
|
{
|
|
let oGrapheme = GRAPHEMES[nGraphemeId];
|
|
if (!oGrapheme)
|
|
return 0;
|
|
|
|
return oGrapheme[1] * COEF;
|
|
}
|
|
function GetGraphemeBBox(graphemeId, fontSize, dpi)
|
|
{
|
|
let grapheme = GRAPHEMES[graphemeId];
|
|
if (!grapheme || grapheme[2] <= 0)
|
|
return {minX : 0, maxX : 0, minY : 0, maxY : 0};
|
|
|
|
let measurer = AscCommon.g_oTextMeasurer;
|
|
|
|
if (undefined === dpi)
|
|
dpi = 72;
|
|
|
|
let fontId = grapheme[0] >> 8;
|
|
let fontStyle = grapheme[0] & 0xF;
|
|
|
|
let fontName = AscCommon.FontNameMap.GetName(fontId);
|
|
measurer.SetFontInternal(fontName, fontSize, fontStyle, dpi);
|
|
|
|
let bbox = measurer.GetBBox(grapheme[3]);
|
|
|
|
let minX = bbox.fMinX;
|
|
let maxX = bbox.fMaxX;
|
|
let minY = bbox.fMinY;
|
|
let maxY = bbox.fMaxY;
|
|
|
|
if (grapheme[2] > 1)
|
|
{
|
|
let x = 0;
|
|
let y = 0;
|
|
|
|
let grCoeff = COEF * fontSize * dpi / 25.4;
|
|
for (let pos = 3, index = 0, count = grapheme[2]; index < count; ++index)
|
|
{
|
|
let bbox = measurer.GetBBox(grapheme[pos++]);
|
|
|
|
minX = Math.min(minX, x + bbox.fMinX);
|
|
maxX = Math.max(maxX, x + bbox.fMaxX);
|
|
minY = Math.min(minY, y + bbox.fMinY);
|
|
maxY = Math.max(maxY, y + bbox.fMaxY);
|
|
|
|
x += grapheme[pos++] * grCoeff; // advanceX
|
|
y += grapheme[pos++] * grCoeff; // advanceY
|
|
|
|
pos += 3;
|
|
}
|
|
}
|
|
|
|
return {
|
|
minX : minX,
|
|
maxX : maxX,
|
|
minY : minY,
|
|
maxY : maxY
|
|
};
|
|
}
|
|
function GetGraphemeFontId(graphemeId)
|
|
{
|
|
let oGrapheme = GRAPHEMES[graphemeId];
|
|
if (!oGrapheme)
|
|
return null;
|
|
|
|
return oGrapheme[0];
|
|
}
|
|
function GetFontNameByFontId(fontId)
|
|
{
|
|
return AscCommon.FontNameMap.GetName((fontId | 0) >> 8);
|
|
}
|
|
function GetFontStyleByFontId(fontId)
|
|
{
|
|
return ((fontId | 0) & 0xF);
|
|
}
|
|
function GetGraphemeCodePoints(graphemeId)
|
|
{
|
|
let g = GRAPHEMES[graphemeId];
|
|
if (!g)
|
|
return [];
|
|
|
|
if (1 === g[2])
|
|
{
|
|
return g[8];
|
|
}
|
|
else
|
|
{
|
|
let result = [];
|
|
for (let i = 0, pos = 8, count = g[2]; i < count; ++i, pos += 6)
|
|
{
|
|
result = result.concat(g[pos]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
}
|
|
//--------------------------------------------------------export----------------------------------------------------
|
|
window['AscFonts'] = window['AscFonts'] || {};
|
|
window['AscFonts'].NO_GRAPHEME = NO_GRAPHEME;
|
|
window['AscFonts'].InitGrapheme = InitGrapheme;
|
|
window['AscFonts'].DrawGrapheme = DrawGrapheme;
|
|
window['AscFonts'].CompareGraphemes = CompareGraphemes;
|
|
window['AscFonts'].AddGlyphToGrapheme = AddGlyphToGrapheme;
|
|
window['AscFonts'].GetGrapheme = GetGrapheme;
|
|
window['AscFonts'].GetGraphemeWidth = GetGraphemeWidth;
|
|
window['AscFonts'].GetGraphemeBBox = GetGraphemeBBox;
|
|
window['AscFonts'].GetGraphemeFontId = GetGraphemeFontId;
|
|
window['AscFonts'].GetFontNameByFontId = GetFontNameByFontId;
|
|
window['AscFonts'].GetFontStyleByFontId = GetFontStyleByFontId;
|
|
window['AscFonts'].GetGraphemeCodePoints = GetGraphemeCodePoints;
|
|
|
|
})(window);
|