<> <> <> <> <<>> 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 PROCESS _ NIL; 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.GMT _ FS.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] ~ { <> 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] ~ { <> 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 POINTER _ NIL; CreateNewFile: PROC ~ TRUSTED { header: Header _ [ password: passwordValue, numberOfEntries: 0, numberOfWords: SIZE[Header] + SIZE[CARDINAL], totalUsage: 0 ]; pointer: LONG POINTER _ @header; stream: IO.STREAM _ FS.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: BOOLEAN _ FALSE] ~ 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 { <> hdr: LONG POINTER TO Header _ residentPointer; pages: INT _ PagesForWords[hdr.numberOfWords]; couldNotExtend: BOOLEAN _ TRUE; IF WriteCacheToDisk[].couldNotExtend THEN RETURN; residentPointer _ hdr _ NIL; resident _ NIL; SafeStorage.ReclaimCollectibleObjects[]; Process.Pause[Process.MsecToTicks[1000]]; <> 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 INT _ NEW[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 { <> 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; <> maskBitsLimit: INT _ 60000; <> InsertMask: PUBLIC PROC [fontID: FontID, transID: TransID, charCode: INT, sWidth, fWidth: Scaled.Value, mask: Mask, amplified, correctSpace, correctMask: BOOLEAN] RETURNS [ok: BOOLEAN _ FALSE] ~ { bb: ImagerPixelMap.DeviceRectangle _ ImagerMask.BoundingBox[mask]; runCount: INT _ ImagerMask.CountRuns[mask]; rasterBits: INT _ Basics.LongMult[bb.fSize, bb.sSize]; runGroupBits: INT _ SIZE[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 { <> pointer: LONG POINTER _ residentPointer; offset: INT _ SIZE[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 { <> 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: INT _ SIZE[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 { <> <> newpages: INT _ MAX[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]]; <> }; residentPointer _ resident.Pointer; }; OpenFileAndBuildTables: PROC ~ TRUSTED { openOK: BOOLEAN _ TRUE; 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: CARDINAL _ MIN[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: INT _ SIZE[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] ~ { <> 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 { <> used: CARDINAL ~ 0; unused: CARDINAL ~ CARDINAL.LAST; fn, tn: NAT _ 0; <> 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; <> 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; <> 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; <> 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; <> 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; <> 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.STREAM _ FS.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: CARDINAL _ SIZE[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 [BOOL _ TRUE] ~ 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: INT _ SIZE[Header]; running: BOOLEAN _ TRUE; 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: BOOLEAN _ FALSE; 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.