ImagerMaskCacheImpl.mesa
Copyright (C) 1984, 1985 by Xerox Corporation. All rights reserved.
Michael Plass, August 17, 1984 3:57:12 pm PDT
Doug Wyatt, January 26, 1985 5:42:55 pm PST
DIRECTORY
Ascii,
Basics,
BasicTime,
CountedVM,
FS,
ImagerFrameBuffer,
ImagerMaskCache,
ImagerMask,
ImagerPixelMap,
ImagerTransformation,
IO,
Process,
Rope,
SafeStorage,
Scaled,
VM,
ImagerMaskCacheImpl: CEDAR MONITOR
IMPORTS VM, BasicTime, CountedVM, IO, Ascii, FS, ImagerTransformation, Rope, Process, SafeStorage, ImagerMask, ImagerPixelMap, Basics, ImagerFrameBuffer
EXPORTS ImagerMaskCache
~ BEGIN OPEN ImagerMaskCache;
WordsForPages: PROC [pages: INT] RETURNS [INT] ~ {RETURN [VM.WordsForPages[pages]]};
PagesForWords: PROC [words: INT] RETURNS [INT] ~ {RETURN [VM.PagesForWords[words]]};
currentStatus: Status ← disabled;
statusChange: CONDITION;
lockCount: NAT ← 0;
lockOwner: UNSAFE PROCESSNIL;
lockFree: CONDITION;
GetHeader: PUBLIC PROC RETURNS [header: Header] ~ {
Locked: PROC ~ TRUSTED {
status: Status ← GetStatus[waitDuringTransition: TRUE];
IF status = disabled THEN header ← [0, 0, 0, 0]
ELSE {
hdr: LONG POINTER TO Header ← residentPointer;
header ← hdr^
};
};
DoUnderLock[Locked];
};
Lock: ENTRY PROC ~ {
me: UNSAFE PROCESS ~ Process.GetCurrent[];
IF lockOwner # me THEN {
WHILE lockCount # 0 DO WAIT lockFree ENDLOOP;
lockOwner ← me;
};
lockCount ← lockCount + 1;
};
UnLock: ENTRY PROC ~ {
IF lockOwner # Process.GetCurrent[] THEN ERROR;
lockCount ← lockCount - 1;
IF lockCount = 0 THEN {lockOwner ← NIL; NOTIFY lockFree};
};
GetStatus: PUBLIC ENTRY PROC [waitDuringTransition: BOOLEAN] RETURNS [Status] ~ {
WHILE waitDuringTransition AND currentStatus = transition DO
WAIT statusChange;
ENDLOOP;
RETURN [currentStatus]
};
SetStatus: PUBLIC ENTRY PROC [status: Status] ~ {
currentStatus ← status;
BROADCAST statusChange;
};
DoUnderLock: PUBLIC PROC [action: PROC] ~ {
Lock[];
action[ ! UNWIND => UnLock[]];
UnLock[];
};
DoEnabled: PUBLIC PROC [action: PROC] ~ {
Lock[];
BEGIN ENABLE UNWIND => UnLock[];
IF currentStatus = extendable THEN NULL
ELSE Enable[];
action[];
END;
UnLock[];
};
DoReadOnlyOrEnabled: PUBLIC PROC [action: PROC] ~ {
Lock[];
BEGIN ENABLE UNWIND => UnLock[];
IF currentStatus = readOnly OR currentStatus = extendable THEN NULL
ELSE Enable[];
action[];
END;
UnLock[];
};
Disable: PUBLIC PROC ~ {
Lock[];
BEGIN ENABLE UNWIND => UnLock[];
status: Status ← GetStatus[waitDuringTransition: TRUE];
currentStatus ← transition;
IF status = extendable THEN {
IF WriteCacheToDisk[].couldNotExtend THEN NULL;
FS.Close[file];
file ← [NIL];
residentPointer ← NIL;
resident ← NIL;
SafeStorage.ReclaimCollectibleObjects[];
status ← disabled;
};
IF status = readOnly THEN {
FS.Close[file];
file ← [NIL];
residentPointer ← NIL;
resident ← NIL;
SafeStorage.ReclaimCollectibleObjects[];
ResetTables[];
status ← disabled;
};
SetStatus[status];
END;
UnLock[];
};
MakeReadOnly: PUBLIC PROC ~ {
Lock[];
BEGIN ENABLE UNWIND => UnLock[];
status: Status ← GetStatus[waitDuringTransition: TRUE];
currentStatus ← transition;
IF status = extendable THEN {
date: BasicTime.GMT;
IF WriteCacheToDisk[].couldNotExtend THEN {
FS.Close[file];
file ← [NIL];
residentPointer ← NIL;
resident ← NIL;
SafeStorage.ReclaimCollectibleObjects[];
status ← disabled;
}
ELSE {
date ← FS.GetInfo[file].created;
FS.Close[file];
file ← FS.Open[fontCacheName, $read, date];
status ← readOnly;
};
};
IF status = disabled THEN {
OpenFileAndBuildTables[];
status ← readOnly;
};
SetStatus[status];
END;
UnLock[];
};
Enable: PUBLIC PROC ~ {
Lock[];
BEGIN ENABLE UNWIND => UnLock[];
status: Status ← GetStatus[waitDuringTransition: TRUE];
currentStatus ← transition;
IF status = disabled THEN {
OpenFileAndBuildTables[];
status ← extendable;
};
IF status = readOnly THEN {
date: BasicTime.GMTFS.GetInfo[file].created;
FS.Close[file];
file ← FS.Open[fontCacheName, $write, date];
status ← extendable;
};
SetStatus[status];
END;
UnLock[];
};
transTable: TransTable ← NEW[TransTableRep[20]];
TransTable: TYPE ~ REF TransTableRep;
TransTableRep: TYPE ~ RECORD [
length: NAT ← 0,
highWaterMark: NAT ← 0,
seq: SEQUENCE maxLength: NAT OF REF TransformationCodeEntry
];
CheckTransTable: PROC ~ {
-- This is to document the invariants on the transformation table, and is not normally executed.
FOR i: NAT IN [0..transTable.maxLength) DO
ti: REF TransformationCodeEntry ← transTable[i];
ok: BOOL
SELECT i FROM
IN [0..transTable.length) =>
ti.codeValue = i,
-- These have code values associated with them
IN [transTable.length..transTable.highWaterMark) =>
ti.codeValue = CARDINAL.LAST,
-- These currently have no code values, but have been handed out to clients and so must be kept around.
IN [transTable.highWaterMark..transTable.maxLength) =>
ti = NIL,
-- Free slots in the sequence.
ENDCASE => ERROR;
IF NOT ok THEN ERROR;
ENDLOOP;
};
TransformationFromTransID: PUBLIC PROC[transID: TransID] RETURNS[Transformation] ~ {
t: Coefficients ← transID.coefficients;
RETURN [ImagerTransformation.Create[t.a, t.b, t.c, t.d, t.e, t.f]];
};
TransIDFromTransformation: PUBLIC PROC [transformation: Transformation, scanConversionType: ScanConversionType, hint: TransID] RETURNS [transID: TransID] ~ {
t: Transformation ~ transformation;
c: Coefficients ← [t.a, t.b, t.c, t.d, t.e, t.f];
IF hint # NIL AND hint.coefficients = c AND hint.scanConversionType = scanConversionType THEN RETURN [hint];
RETURN [InsertTransformation[c, scanConversionType]];
};
InsertTransformation: PROC [c: Coefficients, scanConversionType: ScanConversionType] RETURNS [transID: TransID] ~ {
Locked: PROC ~ {
FOR i: NAT IN [0..transTable.highWaterMark) DO
ti: TransID ← transTable[i];
IF ti.coefficients = c AND ti.scanConversionType = scanConversionType THEN {transID ← ti; RETURN};
ENDLOOP;
IF transTable.highWaterMark >= transTable.maxLength THEN {
new: TransTable ← NEW[TransTableRep[transTable.maxLength + 20]];
FOR i: NAT IN [0..transTable.highWaterMark) DO
new[i] ← transTable[i];
transTable[i] ← NIL;
ENDLOOP;
new.length ← transTable.length;
new.highWaterMark ← transTable.highWaterMark;
transTable ← new;
};
transID ← transTable[transTable.highWaterMark]
NEW [TransformationCodeEntry ← [CARDINAL.LAST, c, scanConversionType]];
transTable.highWaterMark ← transTable.highWaterMark + 1;
};
DoUnderLock[Locked];
};
SetTransIDCode: PROC [transID: TransID] ~ {
Assigns next available code to transID.
IF transID.codeValue = CARDINAL.LAST THEN {
FOR i: NAT IN [transTable.length..transTable.highWaterMark) DO
IF transTable[i] = transID THEN {
s: REF TransformationCodeEntry ← transTable[transTable.length];
transTable[transTable.length] ← transID;
transTable[i] ← s;
transID.codeValue ← transTable.length;
transTable.length ← transTable.length + 1;
RETURN;
};
ENDLOOP;
ERROR FontCacheInconsistency[$BadTransID, 0];
};
};
GetTransCode: PROC [transID: TransID] RETURNS [transIDCode: CARDINAL] ~ {
IF transID.codeValue = CARDINAL.LAST THEN TRUSTED {
SetTransIDCode[transID];
[] ← AppendEntry[transformationCode, SIZE[TransformationCodeEntry], LOOPHOLE[transID]];
};
transIDCode ← transID.codeValue;
};
fontIDTableOldLength: NAT ← 0;
fontIDTable: FontIDTable ← NEW[FontIDTableRep[20]];
FontIDTable: TYPE ~ REF FontIDTableRep;
FontIDTableRep: TYPE ~ RECORD [
length: NAT ← 0,
highWaterMark: NAT ← 0,
seq: SEQUENCE maxLength: NAT OF REF FontIDCodeEntry
];
CheckFontIDTable: PROC ~ {
-- This is to document the invariants on the fontID table, and is not normally executed.
FOR i: NAT IN [0..fontIDTable.maxLength) DO
fi: REF FontIDCodeEntry ← fontIDTable[i];
ok: BOOL
SELECT i FROM
IN [0..fontIDTable.length) =>
fi.codeValue = i,
-- These have code values associated with them
IN [fontIDTable.length..fontIDTable.highWaterMark) =>
fi.codeValue = CARDINAL.LAST,
-- These currently have no code values, but have been handed out to clients and so must be kept around.
IN [fontIDTable.highWaterMark..fontIDTable.maxLength) =>
fi = NIL,
-- Free slots in the sequence.
ENDCASE => ERROR;
IF NOT ok THEN ERROR;
ENDLOOP;
};
RopeFromFontID: PUBLIC PROC [fontID: FontID] RETURNS [name: ROPE] ~ {
i: NAT ← 0;
append: PROC RETURNS [CHAR] ~ {RETURN [fontID[(i←i+1)-1]]};
name ← Rope.FromProc[fontID.chars, append];
};
GMTFromFontID: PUBLIC PROC [fontID: FontID] RETURNS [createdTime: BasicTime.GMT] ~ {
createdTime ← fontID.createdTime;
};
CreateFontID: PROC [chars: NAT, charProc: PROC [NAT] RETURNS [CHAR], createdTime: BasicTime.GMT] RETURNS [fontID: FontID] ~ {
Match: PROC [fid: REF FontIDCodeEntry] RETURNS [BOOLEAN] ~ {
IF fid.createdTime # createdTime THEN RETURN [FALSE];
IF fid.chars # chars THEN RETURN [FALSE];
FOR i: NAT IN [0..chars) DO
IF fid[i] # charProc[i] THEN RETURN [FALSE]
ENDLOOP;
RETURN [TRUE]
};
Lookup: PROC ~ {
FOR i: NAT IN [0..fontIDTable.highWaterMark) DO
fi: FontID ← fontIDTable[i];
IF Match[fi] THEN {fontID ← fi; RETURN};
ENDLOOP;
fontID ← NEW[FontIDCodeEntry[chars]];
fontID.codeValue ← CARDINAL.LAST;
fontID.createdTime ← createdTime;
FOR i: NAT IN [0..chars) DO
fontID[i] ← charProc[i];
ENDLOOP;
IF fontIDTable.highWaterMark >= fontIDTable.maxLength THEN {
new: FontIDTable ← NEW[FontIDTableRep[fontIDTable.highWaterMark + 20]];
FOR i: NAT IN [0..fontIDTable.highWaterMark) DO
new[i] ← fontIDTable[i];
ENDLOOP;
new.length ← fontIDTable.length;
new.highWaterMark ← fontIDTable.highWaterMark;
fontIDTable ← new;
};
fontIDTable[fontIDTable.highWaterMark] ← fontID;
fontIDTable.highWaterMark ← fontIDTable.highWaterMark + 1;
};
DoUnderLock[Lookup];
};
FontIDFromRopeAndGMT: PUBLIC PROC [name: ROPE, createdTime: BasicTime.GMT] RETURNS [fontID: FontID] ~ {
charProc: PROC [i: NAT] RETURNS [CHAR] ~ {RETURN [Ascii.Lower[name.Fetch[i]]]};
fontID ← CreateFontID[name.Length, charProc, createdTime];
};
FontIDFromFontIDCodeEntry: PROC [f: LONG POINTER TO FontIDCodeEntry] RETURNS [fontID: FontID] ~ TRUSTED {
charProc: SAFE PROC [i: NAT] RETURNS [CHAR] ~ TRUSTED {RETURN [f[i]]};
fontID ← CreateFontID[f.chars, charProc, f.createdTime];
};
SetFontIDCode: PROC [fontID: FontID] ~ {
Assigns next available code to transID.
IF fontID.codeValue = CARDINAL.LAST THEN {
FOR i: NAT IN [fontIDTable.length..fontIDTable.highWaterMark) DO
IF fontIDTable[i] = fontID THEN {
s: REF FontIDCodeEntry ← fontIDTable[fontIDTable.length];
fontIDTable[fontIDTable.length] ← fontID;
fontIDTable[i] ← s;
fontID.codeValue ← fontIDTable.length;
fontIDTable.length ← fontIDTable.length + 1;
RETURN;
};
ENDLOOP;
ERROR FontCacheInconsistency[$BadFontID, 0];
};
};
GetFontCode: PROC [fontID: FontID] RETURNS [fontIDCode: CARDINAL] ~ {
IF fontID.codeValue = CARDINAL.LAST THEN TRUSTED {
SetFontIDCode[fontID];
[] ← AppendEntry[fontIDCode, SIZE[FontIDCodeEntry[fontID.chars]], LOOPHOLE[fontID]];
};
fontIDCode ← fontID.codeValue;
};
fontCacheName: ROPE ← "[]<>FontCache.DontDeleteMe";
file: FS.OpenFile;
resident: CountedVM.Handle ← NIL;
residentPointer: LONG POINTERNIL;
CreateNewFile: PROC ~ TRUSTED {
header: Header ← [
password: passwordValue,
numberOfEntries: 0,
numberOfWords: SIZE[Header] + SIZE[CARDINAL],
totalUsage: 0
];
pointer: LONG POINTER ← @header;
stream: IO.STREAMFS.StreamOpen[fontCacheName, $create];
stream.UnsafePutBlock[[base: pointer, startIndex: 0, count: SIZE[Header]*Basics.bytesPerWord]];
stream.PutChar['\000];
stream.PutChar['\000];
stream.Close;
};
CantAppend: ERROR ~ CODE;
AppendEntry: PROC [entryType: EntryType, entryLengthInWords: CARDINAL, dataPtr: LONG POINTER] RETURNS [offset: INT] ~ {
nwords: CARDINAL ~ entryLengthInWords + SIZE[EntryPrefix];
TryAppend: PROC RETURNS [failed: BOOLEAN] ~ TRUSTED {
hdr: LONG POINTER TO Header ← residentPointer;
destOffset: LONG CARDINAL ← hdr.numberOfWords - SIZE[CARDINAL];
newSize: INT ← hdr.numberOfWords + nwords;
IF newSize < 0 THEN ERROR;
IF newSize > resident.Words THEN failed ← TRUE
ELSE {
ep: LONG POINTER TO EntryPrefix ← residentPointer+destOffset;
ep^ ← [entryType, entryLengthInWords];
PrincOpsUtils.LongCopy[from: dataPtr, nwords: entryLengthInWords, to: residentPointer+destOffset+SIZE[EntryPrefix]];
LOOPHOLE[residentPointer+destOffset+nwords, LONG POINTER TO CARDINAL]^ ← 0;
hdr.numberOfWords ← newSize;
hdr.numberOfEntries ← hdr.numberOfEntries + 1;
offset ← destOffset + SIZE[EntryPrefix];
failed ← FALSE
};
};
IF currentStatus # extendable THEN ERROR CantAppend;
IF TryAppend[].failed THEN {
WriteOutAndReadIn[];
IF TryAppend[].failed THEN ERROR CantAppend;
};
};
WriteCacheToDisk: PROC RETURNS [couldNotExtend: BOOLEANFALSE] ~ TRUSTED {
hdr: LONG POINTER TO Header ← residentPointer;
pages: INT ← PagesForWords[hdr.numberOfWords];
FS.SetPageCount[file, pages
! FS.Error => IF error.group = environment THEN {couldNotExtend ← TRUE; CONTINUE}
];
IF couldNotExtend THEN RETURN;
FS.Write[file: file, to: 0, nPages: pages, from: residentPointer];
FS.SetByteCountAndCreatedTime[file, hdr.numberOfWords*Basics.bytesPerWord, BasicTime.Now[]];
};
WriteOutAndReadIn: PROC ~ TRUSTED {
Writes out the resident version, reallocates resident vm, and reads the resident version back in. The data structures do not need to change for this operation.
If it could not extend the file, it returns without doing anything.
hdr: LONG POINTER TO Header ← residentPointer;
pages: INT ← PagesForWords[hdr.numberOfWords];
couldNotExtend: BOOLEANTRUE;
IF WriteCacheToDisk[].couldNotExtend THEN RETURN;
residentPointer ← hdr ← NIL;
resident ← NIL;
SafeStorage.ReclaimCollectibleObjects[];
Process.Pause[Process.MsecToTicks[1000]];
The pause is in the hope that finalization will free up the old vm before reallocation.
AllocateResidentCacheSpace[minPages: pages];
FS.Read[file: file, from: 0, nPages: pages, to: residentPointer];
};
FontCacheInconsistency: PUBLIC ERROR [reason: ATOM, wordOffset: INT] ~ CODE;
hashEntries: INT ← 0;
maxProbes: NAT ← 100;
hashTableSize: NAT ~ 32749; -- a prime
hashTable: REF ARRAY [0..hashTableSize) OF INTNEW[ARRAY [0..hashTableSize) OF INT];
stats: RECORD [sLookups, sProbes, uLookups, uProbes, fLookups: LONG CARDINAL ← 0];
HashTableLookup: PROC [fontIDCode, transformationCode: CARDINAL, charCode: INT]
RETURNS [hashIndex: CARDINAL] ~ TRUSTED {
The first probe is intentionally biased to hit the first part of the table; for the simple (common) case of no more than 8 font names, 8 transformations, and char codes in the normal range, most of the hash table need not be swapped in. Successive probes are generated by a multiplicative-congruence random number generator.
OPEN stats;
rand: CARDINAL ← Basics.LowHalf[LOOPHOLE[charCode]] + Basics.HighHalf[LOOPHOLE[charCode]]*1024 + fontIDCode + fontIDCode*32 + transformationCode * 2048 - transformationCode;
h0: CARDINAL ← fontIDCode MOD 8 + transformationCode MOD 8 * 8 + (Basics.LowHalf[LOOPHOLE[charCode]] MOD 128 - 32)* 64;
WHILE h0 >= hashTableSize DO h0 ← h0 - hashTableSize ENDLOOP;
hashIndex ← h0;
FOR i: CARDINAL IN [0..hashTableSize) DO
t: INT ← hashTable[hashIndex];
IF t = 0 THEN {uProbes ← uProbes + (i + 1); uLookups ← uLookups + 1; RETURN}
ELSE {
te: LONG POINTER TO MaskEntry ← residentPointer + t;
IF te.fontIDCode = fontIDCode
AND te.transformationCode = transformationCode
AND te.charCode = charCode
THEN {sProbes ← sProbes + (i + 1); sLookups ← sLookups + 1; RETURN};
};
rand ← Basics.LongDivMod[Basics.LongMult[3141, rand] + 271, hashTableSize].remainder;
IF (hashIndex ← h0 + rand) >= hashTableSize THEN hashIndex ← hashIndex - hashTableSize;
ENDLOOP;
hashIndex ← CARDINAL.LAST;
fLookups ← fLookups + 1;
};
runGroupOverhead: INT ← 3600;
Used only in deciding which representation to use; a big number weights the choice in favor of rasters.
maskBitsLimit: INT ← 60000;
Used in deciding whether to cache the mask or not
InsertMask: PUBLIC PROC [fontID: FontID, transID: TransID, charCode: INT, sWidth, fWidth: Scaled.Value, mask: Mask, amplified, correctSpace, correctMask: BOOLEAN] RETURNS [ok: BOOLEANFALSE] ~ {
bb: ImagerPixelMap.DeviceRectangle ← ImagerMask.BoundingBox[mask];
runCount: INT ← ImagerMask.CountRuns[mask];
rasterBits: INT ← Basics.LongMult[bb.fSize, bb.sSize];
runGroupBits: INTSIZE[Run]*16*runCount + runGroupOverhead;
maskEntryRef: REF MaskEntry ← NIL;
nwords: CARDINAL;
IF MIN[runGroupBits, rasterBits] > maskBitsLimit THEN RETURN [FALSE];
maskEntryRef ← IF runGroupBits < rasterBits THEN GetRuns[mask, bb, runCount] ELSE GetRaster[mask, bb];
WITH maskEntryRef SELECT FROM
ra: REF MaskEntry.raster => nwords ← SIZE[MaskEntry.raster[ra.sSizeBB*((ra.fSizeBB+Basics.bitsPerWord-1)/Basics.bitsPerWord)]];
ru: REF MaskEntry.runs => nwords ← SIZE[MaskEntry.runs[ru.nRuns]];
ENDCASE => ERROR;
maskEntryRef.charCode ← charCode;
maskEntryRef.usage ← 0;
maskEntryRef.sWidth ← sWidth;
maskEntryRef.fWidth ← fWidth;
maskEntryRef.amplified ← amplified;
maskEntryRef.correctSpace ← correctSpace;
maskEntryRef.correctMask ← correctMask;
BEGIN
Insert: PROC ~ TRUSTED {
offset: INT;
hashIndex: CARDINAL;
maskEntryRef.fontIDCode ← GetFontCode[fontID];
maskEntryRef.transformationCode ← GetTransCode[transID];
hashIndex ← HashTableLookup[maskEntryRef.fontIDCode, maskEntryRef.transformationCode, maskEntryRef.charCode];
IF hashIndex = CARDINAL.LAST THEN ok ← FALSE
ELSE IF hashTable[hashIndex] = 0 THEN {
offset ← AppendEntry[mask, nwords, LOOPHOLE[maskEntryRef]];
hashTable[hashIndex] ← offset;
ok ← TRUE;
};
};
DoReadOnlyOrEnabled[Insert ! CantAppend => CONTINUE];
IF NOT ok THEN {
header: Header ← GetHeader[];
TrimCache[maxNumberOfMasks: INT[hashTableSize]*8/10, maxNumberOfWords: header.numberOfWords*8/10];
DoReadOnlyOrEnabled[Insert ! CantAppend => CONTINUE];
};
END;
};
GetRuns: PROC [mask: Mask, bb: ImagerPixelMap.DeviceRectangle, runCount: INT] RETURNS [REF MaskEntry] ~ {
maxRuns: NAT ← runCount+bb.sSize;
maskEntryRef: REF MaskEntry.runs ← NEW[MaskEntry.runs[maxRuns]];
i: NAT ← 0;
s: INTEGER ← bb.sMin;
AppendRun: PROC [sMin, fMin: INTEGER, fSize: NAT] ~ {
WHILE s # sMin DO
IF s > sMin THEN ERROR;
IF i = 0 THEN {s ← bb.sMin ← bb.sMin + 1; bb.sSize ← bb.sSize - 1}
ELSE IF NOT maskEntryRef[i-1].lastRun THEN {
maskEntryRef[i-1].lastRun ← TRUE;
s ← s + 1;
}
ELSE {
IF i >= maxRuns THEN ERROR;
maskEntryRef[i] ← [fMin: 0, lastRun: TRUE, fSize: 0];
s ← s + 1;
i ← i + 1;
};
ENDLOOP;
IF i >= maxRuns THEN ERROR;
maskEntryRef[i] ← [fMin: fMin-bb.fMin, lastRun: FALSE, fSize: fSize];
i ← i + 1;
};
ImagerMask.GenerateRuns[mask, mask, AppendRun, 0, 0];
IF i > 0 THEN {maskEntryRef[i-1].lastRun ← TRUE; s ← s + 1};
maskEntryRef.nRuns ← i;
maskEntryRef.sMinBB ← bb.sMin;
maskEntryRef.fMinBB ← bb.fMin;
maskEntryRef.sSizeBB ← s - bb.sMin;
maskEntryRef.fSizeBB ← bb.fSize;
RETURN [maskEntryRef]
};
GetRaster: PROC [mask: Mask, bb: ImagerPixelMap.DeviceRectangle] RETURNS [REF MaskEntry] ~ TRUSTED {
rast: CARDINAL ← (bb.fSize+Basics.bitsPerWord-1)/Basics.bitsPerWord;
words: CARDINAL ← bb.sSize*rast;
maskEntryRef: REF MaskEntry.raster ← NEW[MaskEntry.raster[words]];
pixelMap: ImagerPixelMap.PixelMap ← [sOrigin: bb.sMin, fOrigin: bb.fMin, sMin: 0, fMin: 0, sSize: bb.sSize, fSize: bb.fSize, refRep: NEW[ImagerPixelMap.PixelMapRep ← [ref: resident, pointer: @maskEntryRef[0], words: words, lines: bb.sSize, lgBitsPerPixel: 0, rast: rast]]];
pixelMap.Clear;
ImagerMask.ApplyConstant[mask, mask, pixelMap, 1];
maskEntryRef.sMinBB ← bb.sMin;
maskEntryRef.fMinBB ← bb.fMin;
maskEntryRef.sSizeBB ← bb.sSize;
maskEntryRef.fSizeBB ← bb.fSize;
RETURN [maskEntryRef]
};
AgeUseCounts: PROC ~ TRUSTED {
Assume resident cache is present and locked
pointer: LONG POINTER ← residentPointer;
offset: INTSIZE[Header];
hdr: LONG POINTER TO Header ← residentPointer;
words: INT ← hdr.numberOfWords;
entries: INT ← hdr.numberOfEntries;
newTotal: LONG CARDINAL ← 0;
WHILE offset < words DO
prefix: LONG POINTER TO EntryPrefix ← pointer+offset;
offset ← offset + SIZE[EntryPrefix];
SELECT prefix.entryType FROM
trailer => EXIT;
mask => {
entry: LONG POINTER TO MaskEntry ← pointer + offset;
entry.usage ← entry.usage/2 + entry.usage MOD 2;
newTotal ← newTotal + entry.usage;
};
ENDCASE => NULL;
offset ← offset + prefix.entryLengthInWords;
entries ← entries - 1;
ENDLOOP;
IF offset # words THEN ERROR FontCacheInconsistency[$UnexpectedEndMarker, offset];
IF entries # 0 THEN ERROR FontCacheInconsistency[$WrongNumberOfEntries, offset];
hdr.totalUsage ← newTotal;
};
GetMask: PUBLIC UNSAFE PROC [fontID: FontID, transID: TransID, charCode: INT] RETURNS [maskDesc: MaskDesc ← [NIL, NIL]] ~ UNCHECKED {
Get: PROC ~ TRUSTED {
hashIndex: CARDINAL ← HashTableLookup[fontID.codeValue, transID.codeValue, charCode];
offset: INT;
IF hashIndex # CARDINAL.LAST AND (offset ← hashTable[hashIndex]) # 0 THEN {
me: LONG POINTER TO MaskEntry ← residentPointer + offset;
hdr: LONG POINTER TO Header ← residentPointer;
hdr.totalUsage ← hdr.totalUsage + 1;
IF (me.usage ← me.usage + 1) = CARDINAL.LAST THEN AgeUseCounts[];
maskDesc ← [resident, me];
};
};
DoReadOnlyOrEnabled[Get];
};
GetStringBodyMasks: PUBLIC PROC [
fontID: FontID,
transID: TransID,
stringBodyDesc: StringBodyDesc,
maskProc: UNSAFE PROC [MaskDesc]
] RETURNS [StringBodyDesc] ~ TRUSTED {
Could do this much faster . .
WHILE stringBodyDesc.length # 0 DO
charCode: CARDINAL ← Current[stringBodyDesc];
maskDesc: MaskDesc ← GetMask[fontID, transID, charCode];
IF maskDesc.maskEntryPtr = NIL THEN EXIT;
maskProc[maskDesc];
stringBodyDesc ← Advance[stringBodyDesc];
ENDLOOP;
RETURN [stringBodyDesc]
};
cacheVersion: CARDINAL ← 0;
ResetTables: PROC ~ TRUSTED {
cacheVersion ← cacheVersion + 1;
PrincOpsUtils.LongZero[where: LOOPHOLE[hashTable], nwords: SIZE[ARRAY [0..hashTableSize) OF INT]];
WHILE fontIDTable.length > 0 DO
fontIDTable[fontIDTable.length ← fontIDTable.length - 1].codeValue ← CARDINAL.LAST;
ENDLOOP;
WHILE transTable.length > 0 DO
transTable[transTable.length ← transTable.length - 1].codeValue ← CARDINAL.LAST;
ENDLOOP;
};
BuildTables: PROC ~ TRUSTED {
pointer: LONG POINTER ← residentPointer;
offset: INTSIZE[Header];
hdr: LONG POINTER TO Header ← residentPointer;
words: INT ← hdr.numberOfWords;
entries: INT ← hdr.numberOfEntries;
totalUsage: INT ← hdr.totalUsage;
IF hdr.password # passwordValue THEN
ERROR FontCacheInconsistency[$NotAFontCache, 0];
IF Basics.bytesPerWord*words # FS.GetInfo[file].bytes THEN
ERROR FontCacheInconsistency[$InconsistentLengths, 0];
ResetTables[];
WHILE offset < words DO
prefix: LONG POINTER TO EntryPrefix ← pointer+offset;
offset ← offset + SIZE[EntryPrefix];
SELECT prefix.entryType FROM
trailer => EXIT;
fontIDCode => {
ffidce: LONG POINTER TO FontIDCodeEntry ← pointer+offset;
fontID: FontID ← FontIDFromFontIDCodeEntry[ffidce];
SetFontIDCode[fontID];
IF fontID.codeValue # ffidce.codeValue THEN ERROR FontCacheInconsistency[$FontIDCodesOutOfSequence, offset];
};
transformationCode => {
tce: LONG POINTER TO TransformationCodeEntry ← pointer+offset;
transID: TransID ← InsertTransformation[tce.coefficients, tce.scanConversionType];
SetTransIDCode[transID];
IF transID.codeValue # tce.codeValue THEN ERROR FontCacheInconsistency[$TransformationCodesOutOfSequence, offset];
};
mask => {
maskEntry: LONG POINTER TO MaskEntry ← pointer+offset;
hashIndex: CARDINAL ← HashTableLookup[maskEntry.fontIDCode, maskEntry.transformationCode, maskEntry.charCode];
IF maskEntry.fontIDCode >= fontIDTable.length
OR maskEntry.transformationCode >= transTable.length
THEN FontCacheInconsistency[$MaskWithUndefinedFontOrTransformation, offset];
IF hashIndex # CARDINAL.LAST THEN {
IF hashTable[hashIndex] # 0 THEN
FontCacheInconsistency[$MultiplyDefinedMask, offset];
hashTable[hashIndex] ← offset;
totalUsage ← totalUsage - maskEntry.usage;
};
};
ENDCASE => ERROR FontCacheInconsistency[$BadEntryType, offset];
offset ← offset + prefix.entryLengthInWords;
entries ← entries - 1;
ENDLOOP;
IF offset # words THEN ERROR FontCacheInconsistency[$UnexpectedEndMarker, offset];
IF entries # 0 THEN ERROR FontCacheInconsistency[$WrongNumberOfEntries, offset];
IF totalUsage # 0 THEN ERROR FontCacheInconsistency[$WrongTotalUsage, offset];
};
CVMAllocate: PROC [words: INT] RETURNS [CountedVM.Handle]
~ {RETURN [CountedVM.Allocate[words]]};
AllocateResidentCacheSpace: PROC [minPages: INT] ~ TRUSTED {
This attempts to allocate resident vm to be 26% bigger than minPages.
If it fails to allocate this amount, it decreases its expectations and tries again.
newpages: INTMAX[20, (minPages*126+50)/100];
residentPointer ← NIL;
resident ← NIL;
resident ← CVMAllocate[WordsForPages[newpages] ! VM.CantAllocate => {newpages ← MAX[minPages, bestInterval.count]; CONTINUE}];
IF resident = NIL THEN {
SafeStorage.ReclaimCollectibleObjects[];
Process.Pause[Process.MsecToTicks[2000]];
resident ← CVMAllocate[WordsForPages[newpages]];
Let the error through if we still can't get VM.
};
residentPointer ← resident.Pointer;
};
OpenFileAndBuildTables: PROC ~ TRUSTED {
openOK: BOOLEANTRUE;
pages: INT ← 0;
bytes: INT ← 0;
residentPages: INT ← 0;
file ← FS.Open[fontCacheName, $write ! FS.Error => {openOK ← FALSE; CONTINUE}];
IF NOT openOK THEN {
openOK ← TRUE;
CreateNewFile[];
file ← FS.Open[fontCacheName, $write];
};
[pages: pages, bytes: bytes] ← FS.GetInfo[file];
AllocateResidentCacheSpace[pages];
FS.Read[file: file, from: 0, nPages: pages, to: residentPointer];
BuildTables[];
};
Current: PUBLIC PROC [stringBodyDesc: StringBodyDesc] RETURNS [charCode: INT] ~ {
Fet: PROC [i: INT] RETURNS [CARDINAL] ~ {
char: CHAR;
IF i >= stringBodyDesc.start+stringBodyDesc.length THEN RETURN [0];
WITH stringBodyDesc.stringBody SELECT FROM
r: Rope.ROPE => char ← r.Fetch[i];
t: REF TEXT => char ← t[i];
ENDCASE => ERROR;
RETURN [ORD[char]]
};
charCode ← Fet[stringBodyDesc.start];
IF charCode = 255 THEN {
charCode ← Fet[stringBodyDesc.start+1]*256+Fet[stringBodyDesc.start+2];
}
ELSE charCode ← stringBodyDesc.fontSet*256+charCode;
};
Advance: PUBLIC PROC [stringBodyDesc: StringBodyDesc] RETURNS [StringBodyDesc] ~ {
Fet: PROC [i: INT] RETURNS [CARDINAL] ~ {
char: CHAR;
IF i >= stringBodyDesc.start+stringBodyDesc.length THEN RETURN [0];
WITH stringBodyDesc.stringBody SELECT FROM
r: Rope.ROPE => char ← r.Fetch[i];
t: REF TEXT => char ← t[i];
ENDCASE => ERROR;
RETURN [ORD[char]]
};
code: CARDINAL ← Fet[stringBodyDesc.start];
WHILE code = 225 DO
stringBodyDesc.fontSet ← Fet[stringBodyDesc.start+1];
stringBodyDesc.start ← stringBodyDesc.start+2;
stringBodyDesc.length ← stringBodyDesc.length-2;
ENDLOOP;
stringBodyDesc.start ← stringBodyDesc.start+1;
stringBodyDesc.length ← stringBodyDesc.length-1;
RETURN [stringBodyDesc]
};
TrimCache: PUBLIC PROC [maxNumberOfMasks: INT, maxNumberOfWords: INT] ~ {
enabled: PROC ~ TRUSTED {
hdr: LONG POINTER TO Header ← residentPointer;
maxMasks: CARDINALMIN[hdr.numberOfEntries, hashTableSize];
index: CardSeq ← NEW[CardSeqRep[maxMasks]];
GetMaskIndices[index];
SortByUsage[index];
DiscardExcessEntries[index, maxNumberOfMasks, maxNumberOfWords];
DiscardUnusedCodes[index];
WriteNewFile[index];
Disable[];
};
DoEnabled[enabled];
};
CardSeq: TYPE ~ REF CardSeqRep;
CardSeqRep: TYPE ~ RECORD [length: NAT← 0, c: SEQUENCE maxLength: NAT OF LONG CARDINAL];
GetMaskIndices: PROC [index: CardSeq] ~ TRUSTED {
hdr: LONG POINTER TO Header ← residentPointer;
words: INT ← hdr.numberOfWords;
entries: INT ← hdr.numberOfEntries;
offset: INTSIZE[Header];
WHILE offset < words DO
prefix: LONG POINTER TO EntryPrefix ← residentPointer+offset;
offset ← offset + SIZE[EntryPrefix];
SELECT prefix.entryType FROM
trailer => EXIT;
mask => {
IF index.length < index.maxLength THEN {
index[index.length] ← offset;
index.length ← index.length + 1;
};
};
ENDCASE => NULL;
offset ← offset + prefix.entryLengthInWords;
entries ← entries - 1;
ENDLOOP;
IF offset # words THEN ERROR FontCacheInconsistency[$UnexpectedEndMarker, offset];
IF entries # 0 THEN ERROR FontCacheInconsistency[$WrongNumberOfEntries, offset];
};
shellD: ARRAY [1..11] OF CARDINAL = [1, 4, 13, 40, 121, 364, 1093, 3280, 9841, 29524, 65535];
SortByUsage: PROC [index: CardSeq] ~ {
Shell sort
Key: PROC [i: NAT] RETURNS [CARDINAL] ~ TRUSTED {
entry: LONG POINTER TO MaskEntry ← residentPointer+index[i];
RETURN [entry.usage]
};
length: NAT ← index.length;
passes: NAT ← 1;
UNTIL shellD[passes+2] >= index.length DO passes ← passes+1 ENDLOOP;
FOR pass: NAT DECREASING IN [1..passes] DO
h: NAT ← shellD[pass];
FOR i: NAT IN [0..length) DO
key: CARDINAL ← Key[i];
data: LONG CARDINAL ← index[i];
j: NAT ← i;
WHILE j>=h AND key < Key[j-h] DO
index[j] ← index[j-h];
j ← j-h;
ENDLOOP;
index[j] ← data;
ENDLOOP;
ENDLOOP;
};
DiscardExcessEntries: PROC [index: CardSeq, maxNumberOfMasks: INT, maxNumberOfWords: INT] ~ TRUSTED {
n: INT ← 0;
words: INT ← 0;
maxNumberOfMasks ← MIN[maxNumberOfMasks, index.length];
WHILE n < maxNumberOfMasks DO
prefix: LONG POINTER TO EntryPrefix ← residentPointer + index[n] - SIZE[EntryPrefix];
words ← words + SIZE[EntryPrefix] + prefix.entryLengthInWords;
IF words > maxNumberOfWords THEN EXIT;
n ← n + 1;
ENDLOOP;
index.length ← n;
};
DiscardUnusedCodes: PROC [index: CardSeq] ~ TRUSTED {
Abuses the data structures, but only within this proc.
used: CARDINAL ~ 0;
unused: CARDINAL ~ CARDINAL.LAST;
fn, tn: NAT ← 0;
First mark everything as unused.
FOR i: NAT IN [0..fontIDTable.length) DO
fontIDTable[i].codeValue ← unused;
ENDLOOP;
FOR i: NAT IN [0..transTable.length) DO
transTable[i].codeValue ← unused;
ENDLOOP;
Find those that are used.
FOR i: NAT IN [0..index.length) DO
maskEntry: LONG POINTER TO MaskEntry ← residentPointer+index[i];
fontIDTable[maskEntry.fontIDCode].codeValue ← used;
transTable[maskEntry.transformationCode].codeValue ← used;
ENDLOOP;
Give the used ones their new codes.
FOR i: NAT IN [0..fontIDTable.length) DO
IF fontIDTable[i].codeValue = used THEN {fontIDTable[i].codeValue ← fn; fn ← fn+1};
ENDLOOP;
tn ← 0;
FOR i: NAT IN [0..transTable.length) DO
IF transTable[i].codeValue = used THEN {transTable[i].codeValue ← tn; tn ← tn + 1};
ENDLOOP;
Put the new codes in the mask entries.
FOR i: NAT IN [0..index.length) DO
maskEntry: LONG POINTER TO MaskEntry ← residentPointer+index[i];
maskEntry.fontIDCode ← fontIDTable[maskEntry.fontIDCode].codeValue;
maskEntry.transformationCode ← transTable[maskEntry.transformationCode].codeValue;
ENDLOOP;
Shuffle the entries into their rightful places in the table.
FOR i: NAT IN [0..fontIDTable.length) DO
dest: CARDINAL ← fontIDTable[i].codeValue;
IF dest # unused THEN {
s: REF FontIDCodeEntry ← fontIDTable[i];
fontIDTable[i] ← fontIDTable[dest];
fontIDTable[dest] ← s;
};
ENDLOOP;
FOR i: NAT IN [0..transTable.length) DO
dest: CARDINAL ← transTable[i].codeValue;
IF dest # unused THEN {
s: REF TransformationCodeEntry ← transTable[i];
transTable[i] ← transTable[dest];
transTable[dest] ← s;
};
ENDLOOP;
Finally, fix up the lengths.
fontIDTable.length ← fn;
transTable.length ← tn;
};
WriteNewFile: PROC [index: CardSeq] ~ TRUSTED {
header: Header ← [
password: passwordValue,
numberOfEntries: 0,
numberOfWords: SIZE[Header],
totalUsage: 0
];
pointer: LONG POINTER ← @header;
stream: IO.STREAMFS.StreamOpen[fontCacheName, $create];
stream.UnsafePutBlock[[base: pointer, startIndex: 0, count: SIZE[Header]*Basics.bytesPerWord]];
header ← PutNamesAndTransformations[stream, header];
header ← CopySelectedMasks[stream, index, header];
stream.PutChar['\000];
stream.PutChar['\000];
header.numberOfWords ← header.numberOfWords + SIZE[CARDINAL];
stream.SetIndex[0];
stream.UnsafePutBlock[[base: pointer, startIndex: 0, count: SIZE[Header]*Basics.bytesPerWord]];
stream.Close;
};
PutNamesAndTransformations: PROC [stream: IO.STREAM, header: Header] RETURNS [Header] ~ TRUSTED {
FOR i: NAT IN [0..fontIDTable.length) DO
f: REF FontIDCodeEntry ← fontIDTable[i];
words: CARDINALSIZE[FontIDCodeEntry[f.chars]];
prefix: EntryPrefix ← [fontIDCode, words];
prefixPtr: LONG POINTER ← @prefix;
stream.UnsafePutBlock[[base: prefixPtr, startIndex: 0, count: SIZE[EntryPrefix]*Basics.bytesPerWord]];
stream.UnsafePutBlock[[base: LOOPHOLE[f], startIndex: 0, count: words*Basics.bytesPerWord]];
header.numberOfEntries ← header.numberOfEntries + 1;
header.numberOfWords ← header.numberOfWords + SIZE[EntryPrefix] + words;
ENDLOOP;
FOR i: NAT IN [0..transTable.length) DO
t: REF TransformationCodeEntry ← transTable[i];
words: CARDINAL ~ SIZE[TransformationCodeEntry];
prefix: EntryPrefix ← [transformationCode, words];
prefixPtr: LONG POINTER ← @prefix;
stream.UnsafePutBlock[[base: prefixPtr, startIndex: 0, count: SIZE[EntryPrefix]*Basics.bytesPerWord]];
stream.UnsafePutBlock[[base: LOOPHOLE[t], startIndex: 0, count: words*Basics.bytesPerWord]];
header.numberOfEntries ← header.numberOfEntries + 1;
header.numberOfWords ← header.numberOfWords + SIZE[EntryPrefix] + words;
ENDLOOP;
RETURN [header]
};
CopySelectedMasks: PROC [stream: IO.STREAM, index: CardSeq, header: Header] RETURNS [Header] ~ TRUSTED {
FOR i: NAT IN [0..index.length) DO
offset: LONG CARDINAL ← index[i];
prefix: LONG POINTER TO EntryPrefix ← residentPointer+offset-SIZE[EntryPrefix];
maskEntry: LONG POINTER TO MaskEntry ← residentPointer+offset;
stream.UnsafePutBlock[[base: LOOPHOLE[prefix], startIndex: 0, count: (SIZE[EntryPrefix] + prefix.entryLengthInWords)*Basics.bytesPerWord]];
header.numberOfEntries ← header.numberOfEntries + 1;
header.numberOfWords ← header.numberOfWords + SIZE[EntryPrefix] + prefix.entryLengthInWords;
header.totalUsage ← header.totalUsage + maskEntry.usage;
ENDLOOP;
RETURN [header]
};
GetListOfContents: PUBLIC PROC RETURNS [list: LIST OF MaskDescriptor ← NIL] ~ {
SeqOfRope: TYPE ~ RECORD[SEQUENCE length: NAT OF ROPE];
n: REF SeqOfRope ← NEW[SeqOfRope[fontIDTable.length]];
cons: PROC [fontID: FontID, transID: TransID, maskDesc: MaskDesc] RETURNS [BOOLTRUE] ~ TRUSTED {
entry: LONG POINTER TO MaskEntry ← maskDesc.maskEntryPtr;
IF n[entry.fontIDCode] = NIL THEN n[entry.fontIDCode] ← RopeFromFontID[fontIDTable[entry.fontIDCode]];
list ← CONS[[
name: NARROW[n[entry.fontIDCode]],
transID: transID,
usage: entry.usage,
set: entry.charCode/256,
c: VAL[Basics.LowHalf[entry.charCode] MOD 256]
], list];
};
Enumerate[cons];
};
Enumerate: PUBLIC PROC [action: PROC [FontID, TransID, MaskDesc] RETURNS [continue: BOOL]] ~ {
Enumerate: PROC ~ TRUSTED {
hdr: LONG POINTER TO Header ← residentPointer;
words: INT ← hdr.numberOfWords;
entries: INT ← hdr.numberOfEntries;
offset: INTSIZE[Header];
running: BOOLEANTRUE;
WHILE running AND offset < words DO
prefix: LONG POINTER TO EntryPrefix ← residentPointer+offset;
offset ← offset + SIZE[EntryPrefix];
SELECT prefix.entryType FROM
trailer => EXIT;
mask => {
entry: LONG POINTER TO MaskEntry ← residentPointer+offset;
running ← action[fontIDTable[entry.fontIDCode], transTable[entry.transformationCode], [resident, entry]];
};
ENDCASE => NULL;
offset ← offset + prefix.entryLengthInWords;
entries ← entries - 1;
ENDLOOP;
};
DoReadOnlyOrEnabled[Enumerate];
};
SetMaskUsage: PUBLIC PROC [fontID: FontID, transID: TransID, charCode: INT, usage: CARDINAL] ~ {
Proc: PROC ~ TRUSTED {
maskDesc: MaskDesc ← GetMask[fontID, transID, charCode];
IF maskDesc.maskEntryPtr # NIL THEN {
hdr: LONG POINTER TO Header ← residentPointer;
hdr.totalUsage ← hdr.totalUsage - maskDesc.maskEntryPtr.usage + usage;
maskDesc.maskEntryPtr.usage ← usage;
IF usage = CARDINAL.LAST THEN AgeUseCounts[];
};
};
DoReadOnlyOrEnabled[Proc];
};
showHash: BOOLEANFALSE;
ShowHash: PROC ~ TRUSTED {
showHash ← TRUE;
Process.Detach[FORK ShowHashProcess[]];
};
ShowHashProcess: PROC ~ TRUSTED {
pm: ImagerPixelMap.PixelMap ← ImagerPixelMap.Create[0, [808-64, 256, 64, 512]];
display: ImagerPixelMap.PixelMap ← ImagerFrameBuffer.LFDisplay[];
b: LONG POINTER TO PACKED ARRAY [0..32767] OF [0..1] ← pm.refRep.pointer;
WHILE showHash DO
Process.Pause[Process.MsecToTicks[500]];
pm.Clear;
FOR i: NAT IN [0..hashTableSize) DO
IF hashTable[i] > 0 THEN b[i] ← 1;
ENDLOOP;
display.Transfer[pm];
ENDLOOP;
};
END.