📄 usplib.c
字号:
//
// UspLib.c
//
// USPLIB is a Unicode text-display support library. It is a wrapper
// around the low-level Uniscribe API and provides routines
// used to display Unicode text with the ability to
// apply styles (colours/fonts etc) with a user-supplied attribute-run-list
//
// UspAllocate
// UspFree
// UspAnalyze
//
// UspApplySelection
// UspApplyAttributes
//
// Written by J Brown 2006 Freeware
// www.catch22.net
//
#define _WIN32_WINNT 0x501
#ifndef _UNICODE
#define _UNICODE
#endif
#ifndef UNICODE
#define UNICODE
#endif
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <usp10.h>
#include <tchar.h>
#include <stdlib.h>
#include <malloc.h>
#include "usplib.h"
// UspCtrl.c
int CtrlCharWidth(USPFONT *uspFont, HDC hdc, ULONG chValue);
//
// Return an item-run based on visual-order
//
ITEM_RUN *GetItemRun(USPDATA *uspData, int visualIdx)
{
int logicalIdx = uspData->visualToLogicalList[visualIdx];
return &uspData->itemRunList[logicalIdx];
}
//
// Locate tabs in the original character array, and then modify the
// corresponding glyph-width (which will be zero) to a value reflecting
// the size of the tab in pixels. This is all that is required to support
// tabs, the mouse+drawing routines just treat them as regular glyphs
// after this.
//
static
BOOL ExpandTabs(USPDATA *uspData, WCHAR *wstr, int wlen, SCRIPT_TABDEF *tabdef)
{
int i;
int xpos = tabdef->iTabOrigin;
int tabidx = 0;
int tabWidth;
int tab;
// calculate average character-width
int charWidth = uspData->uspFontList ? uspData->uspFontList[0].tm.tmAveCharWidth :
uspData->defaultFont.tm.tmAveCharWidth;
// validate the SCRIPT_TABDEF structure
if(tabdef->cTabStops != 0 && tabdef->pTabStops == 0 || tabdef->cTabStops < 0)
return FALSE;
// All tab-stops are the length of the first entry in pTabStops
if(tabdef->cTabStops == 1)
{
tabWidth = tabdef->pTabStops[0];
}
// Tab-stops occur every eight average-character widths
else
{
tabWidth = charWidth;
}
// Do the scaling
if(tabdef->iScale != 0)
{
xpos *= tabdef->iScale / 4;
tabWidth *= tabdef->iScale / 4;
}
else
{
xpos *= charWidth;
tabWidth *= charWidth;
}
// scan item-runs in *visual* order (think this is right!)
for(i = 0; i < uspData->itemRunCount; i++)
{
ITEM_RUN *itemRun = GetItemRun(uspData, i);
// match tabs
if(wstr[itemRun->charPos] == '\t')
{
// calculate distance to next tab-stop position
if(tabdef->cTabStops <= 1)
{
tab = tabWidth - (xpos % tabWidth);
}
else
{
// search for the next tab-stop position
for( ; tabidx < tabdef->cTabStops; tabidx++)
{
if(xpos > tabdef->pTabStops[tabidx])
{
tab = 0;
break;
}
}
//tab = uspData->
}
uspData->widthList[itemRun->glyphPos] = tab;
itemRun->width = tab;
itemRun->tab = TRUE;
}
xpos += itemRun->width;
}
return TRUE;
}
//
// Non-complex scripts can be merged into a single item
//
static
int MergeSimpleScripts(SCRIPT_ITEM *itemList, int itemCount)
{
// global script-table, used for merging non-complex runs together :)
SCRIPT_PROPERTIES **propList;
int propCount;
int i;
// get pointer to the global script table
ScriptGetProperties(&propList, &propCount);
// coalesce item-runs that are based on simple-scripts
for(i = 0; i < itemCount - 1; i++)
{
// use each item-run's SCRIPT_ANALYSIS::eScript member to lookup the
// appropriate script in the global-table
if(propList[itemList[i+0].a.eScript]->fComplex == FALSE &&
propList[itemList[i+1].a.eScript]->fComplex == FALSE)
{
// be careful which SCRIPT_ITEM we overwrite as we need to
// maintain correct iCharPos members
memmove(&itemList[i+1], &itemList[i+2], (itemCount-i-1) * sizeof(SCRIPT_ITEM));
itemCount--;
itemList[i].a.eScript = SCRIPT_UNDEFINED;
}
}
return itemCount;
}
//
// Wrapper around ScriptItemize. Merges the results of ScriptItemize with
// "application-defined style runs". In our case, we take the ATTR[] array
// of runs and inspect the font and control-character flags *only* - colours
// and selection flags are *not* used to split the runs.
//
// Note that for scripts that do *not* have the fComplex property set, (obtained
// via ScriptGetProperties) we can merge item-runs together to form a single
// run with SCRIPT_UNDEFINED...this makes text-display *much* faster for simple
// scripts such as english.
//
static
BOOL BuildMergedItemRunList (
USPDATA * uspData,
WCHAR * wstr,
int wlen,
ATTR * attrList,
SCRIPT_CONTROL * scriptControl,
SCRIPT_STATE * scriptState
)
{
// attribute-list index+position
int a = 0;
int attrPos = 0;
int attrLen = 0;
ATTR attr = attrList[0];
// item-list index+position (only needed in this function)
int i = 0;
int itemPos;
int itemLen;
int itemCount;
SCRIPT_ITEM * itemList = uspData->tempItemList;
int allocLen = max(uspData->tempItemAllocLen, 16);
// merged-list index+position
int m = 0;
int mergePos = 0;
ITEM_RUN * mergedList = uspData->itemRunList;
int mergedAllocLen = uspData->itemRunAllocLen;
HRESULT hr;
// Create the Uniscribe SCRIPT_ITEM list which just describes
// the spans of plain unicode text (grouped by script)
do
{
// allocate memory for SCRIPT_ITEM list
if(uspData->tempItemAllocLen < allocLen)
{
itemList = realloc(itemList, allocLen * sizeof(SCRIPT_ITEM));
if(itemList == 0)
return FALSE;
// store this temporary-item list so we don't have to free/alloc all the time
uspData->tempItemAllocLen = allocLen;
uspData->tempItemList = itemList;
}
hr = ScriptItemize(
wstr,
wlen,
allocLen,
scriptControl,
scriptState,
itemList,
&itemCount
);
if(hr != S_OK && hr != E_OUTOFMEMORY)
return FALSE;
allocLen *= 2;
}
while(hr != S_OK);
// any non-complex scripts can be merged into a single item
// itemCount could be decremented after this call
itemCount = MergeSimpleScripts(itemList, itemCount);
//
// Merge SCRIPT_ITEMs with the ATTR runlist to produce finer-grained
// item-runs which describes spans of text *and* style
//
while(i < itemCount)
{
if(attrPos + attrLen < wlen)
attr = attrList[a];
// grow the merge-list if necessary
if(m >= mergedAllocLen)
{
mergedAllocLen += 16;
mergedList = realloc(mergedList, mergedAllocLen * sizeof(ITEM_RUN));
}
// build an ITEM_RUN with default settings
ZeroMemory(&mergedList[m], sizeof(ITEM_RUN));
mergedList[m].analysis = itemList[i].a;
mergedList[m].font = attr.font;
mergedList[m].ctrl = attr.ctrl;
mergedList[m].eol = attr.eol;
// control-characters have their Unicode code-point stored in the item-run
// (there will always be 1 ctrlchar per run)
if(mergedList[m].ctrl)
mergedList[m].chcode = wstr[mergePos];
// safeguard against incorrect font usage when the user didn't specify a font-list
if(uspData->uspFontList == &uspData->defaultFont)
mergedList[m].font = 0;
itemPos = itemList[i].iCharPos;
itemLen = itemList[i+1].iCharPos - itemPos;
//
// coalesce identical attribute cells into one contiguous run. Ignore all style
// attributes except the ::font flags so even ATTR with different colours
// will be merged together if they share the same font.
//
// However, any ATTR with the ::ctrl flag set will be isolated as a single
// ITEM_RUN representing one control-character exactly.
//
if(attrLen == 0)
{
while(attrPos + attrLen < wlen)
{
attrLen += attrList[a].len;
if(attrPos + attrLen < wlen &&
(attrList[a].font != attrList[a+1].font ||
attrList[a].ctrl != attrList[a+1].ctrl ||
attrList[a].eol != attrList[a+1].eol ||
attrList[a].ctrl ||
attrList[a].eol)
)
break;
// skip to next (coalesce)
a++;
}
}
// Detect overlapping run boundaries
if(attrPos+attrLen < itemPos+itemLen)
{
mergedList[m].charPos = mergePos;
mergedList[m].len = (attrPos+attrLen) - mergePos;
attrPos += attrLen;
attrLen = 0;
a++;
}
else if(attrPos+attrLen >= itemPos+itemLen)
{
mergedList[m].charPos = mergePos;
mergedList[m].len = (itemPos+itemLen) - mergePos;
i++;
if(attrPos+attrLen == itemPos+itemLen)
{
attrPos += attrLen;
attrLen = 0;
a++;
}
}
// advance position in merge-list
mergePos += mergedList[m].len;
m++;
}
// store the results
uspData->itemRunList = mergedList;
uspData->itemRunCount = m;
uspData->itemRunAllocLen = mergedAllocLen;
return TRUE;
}
static
BOOL BuildVisualMapping (
ITEM_RUN * mergedRunList,
int mergedRunCount,
BYTE bidiLevels[],
int visualToLogicalList[]
)
{
int i;
// Manually extract bidi-embedding-levels ready for ScriptLayout
for(i = 0; i < mergedRunCount; i++)
bidiLevels[i] = (BYTE)mergedRunList[i].analysis.s.uBidiLevel;
// Build a visual-to-logical mapping order
ScriptLayout(
mergedRunCount,
bidiLevels,
visualToLogicalList,
0
);
return TRUE;
}
/*
//
// Reverse logical-cluster array, and the clusters within the array
// For RTL runs, this changes the logical-cluster list to visual-order
//
// Unused but left just in-case I need it again
//
static
void ReverseClusterRun(WORD *sourceList, WORD *destList, int runLen)
{
int i, lasti;
for(i = runLen - 1, lasti = i; i >= -1; i--)
{
if(i == -1 || sourceList[i] != sourceList[lasti])
{
int clusterlen = lasti - i;
int pos = sourceList[lasti] - (clusterlen - 1);
while(clusterlen--)
*destList++ = pos;
lasti = i;
}
}
}*/
//
// Call ScriptShape and ScriptPlace to return glyph information
// for the specified run of text.
//
static
BOOL ShapeAndPlaceItemRun(USPDATA *uspData, ITEM_RUN *itemRun, HDC hdc, WCHAR *wrunstr)
{
ABC abc;
HRESULT hr;
USPFONT * uspFont = 0;
HANDLE holdFont = 0;
int reallocSize = 0;
// select the appropriate font
uspFont = &uspData->uspFontList[itemRun->font];
holdFont = SelectObject(hdc, uspFont->hFont);
// glyph data for this run is appended to the end
itemRun->glyphPos = uspData->glyphCount;
//
// Generate glyph information for each character in the run
// keep looping until we find a buffer big enough
//
do
{
// guess at 1.5x the run-length
reallocSize += itemRun->len * 3 / 2;
// perform memory allocations. Let ScriptShape catch any alloc-failures
if(uspData->glyphCount + reallocSize >= uspData->glyphAllocLen)
{
uspData->glyphAllocLen += reallocSize;
uspData->glyphList = realloc(uspData->glyphList, uspData->glyphAllocLen * sizeof(WORD));
uspData->offsetList = realloc(uspData->offsetList, uspData->glyphAllocLen * sizeof(GOFFSET));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -