EXPORTS UnifiedFonts = {
OPEN UnifiedFonts;
nullBitBltTable: BitBlt.BitBltTable = [dst: [word: NIL, bit: 0], dstBpl: 0, src: [word: NIL, bit: 0], srcDesc: [srcBpl[0]], width: 0, height: 0, flags: [disjoint: TRUE, gray: FALSE, srcFunc: null, dstFunc: or]];
IsIn:
PROCEDURE [b, c: Box]
RETURNS [
BOOLEAN] =
--
INLINE
-- {
RETURN[
b.xmin>=c.xmin AND b.ymin>=c.ymin AND b.xmax<=c.xmax AND b.ymax<=c.ymax
]};
TranslationOnly:
PROCEDURE [s: CGMatrix.Ref]
RETURNS [
BOOLEAN] =
--
INLINE
-- {
RETURN[
s.m.a = 1.0 AND s.m.b = 0.0 AND s.m.c = 0.0 AND s.m.d = -1.0
]};
Floor: PROC [r: REAL] RETURNS [i: INTEGER] = {i ← Real.Fix[r-FIRST[INTEGER]] + FIRST[INTEGER]};
Ceiling: PROC [r: REAL] RETURNS [i: INTEGER] = {i ← -Floor[-r]};
Position:
TYPE =
MACHINE
DEPENDENT
RECORD [
SELECT
OVERLAID *
FROM
scaled => [sv: Scaled.Value],
splitIn3 => [
frac: CARDINAL,
wordOffset: [0..LAST[WORD]/(Environment.bitsPerWord)],
bit: [0..Environment.bitsPerWord)
],
ENDCASE];
showWhite: BOOLEAN ← FALSE;
GetData: PROC [context: Context] RETURNS [CGContext.Ref] = {RETURN[NARROW[context.data]]};
blting, drawbiting, innerlooping: BOOLEAN ← TRUE; -- for timing experiments
trimmingWidth, trimmingHeight: BOOLEAN ← TRUE;
DrawCharSeq:
PUBLIC
PROCEDURE [
font: FONT, context: Context, count: NAT,
charPtr: LONG POINTER TO CHAR, charIncrement: NAT,
deltaXptr: LONG POINTER TO FixedUnit, deltaXincrement: NAT,
deltaYptr: LONG POINTER TO FixedUnit, deltaYincrement: NAT,
positioning: Positioning ← rounded
] = TRUSTED {
ENABLE SafeStorage.NarrowFault, SafeStorage.NarrowRefFault => GOTO Oops;
halfPoint: CARDINAL = (FixedPoint.fraction/2);
contextData: CGContext.Ref ← GetData[context];
clientMatrix: CGMatrix.Ref ← contextData.matrix;
device: CGDevice.Ref ← contextData.device;
clipper: CGClipper.Ref ← contextData.clipper;
IF font.bitmapCacheable
AND contextData.yUp
AND TranslationOnly[clientMatrix]
AND (contextData.src.color = Graphics.black OR contextData.src.color = Graphics.white)
THEN {
SetCP:
PROC [positioning: Positioning] =
TRUSTED {
contextData.cp ←
IF positioning = exact
THEN [xPosition.sv.MINUS[Scaled.half].Float[], yPosition.sv.MINUS[Scaled.half].Float[]]
ELSE [xPosition.sv.Floor[], yPosition.sv.Floor[]]
};
easy:
BOOLEAN ← contextData.haveRaster
AND positioning = rounded
AND contextData.yUp
AND CGClipper.IsBox[clipper];
bc: CHAR ← font.bc;
ec: CHAR ← font.ec;
bbspace: BitBlt.BBTableSpace;
bb: BitBlt.BitBltTablePtr ← BitBlt.AlignedBBTable[@bbspace];
characterCache: CharacterCache ← GetCharacterCache[font];
deviceRasterBase: LONG POINTER ← contextData.dbase;
leftPixel: INTEGER ← Ceiling[clipper.box.xmin];
rightPixel: INTEGER ← Floor[clipper.box.xmax];
minAllowedBaseline: INTEGER ← Ceiling[clipper.box.ymin] - characterCache.ymin;
maxAllowedBaseline: INTEGER ← Floor[clipper.box.ymax] - characterCache.ymax;
xPosition, yPosition: Position;
This is the position of the origin of the character bounding box, in device coordinates;
actually offset by half a pixel to allow truncation instead of rounding in the inner loop.
xPosition.sv ← Scaled.FromReal[contextData.cp.x+0.5];
yPosition.sv ← Scaled.FromReal[contextData.cp.y+0.5];
bb^ ← nullBitBltTable;
IF contextData.src.mode = invert THEN bb.flags.dstFunc ← xor
ELSE
IF contextData.src.color = Graphics.white
THEN
{bb.flags.srcFunc ← complement; bb.flags.dstFunc ← and};
IF showWhite THEN bb.flags.dstFunc ← null;
bb.dstBpl ← contextData.drast * Environment.bitsPerWord;
bb.srcDesc.srcBpl ← characterCache.rasterBits;
WHILE count>0
DO
pointerBase: LONG POINTER ← contextData.dbase + contextData.drast*INT[yPosition.sv.integerPart+characterCache.ymin];
IF easy
AND deltaYptr^ = Scaled.zero AND deltaYincrement = 0
AND yPosition.sv.integerPart IN [minAllowedBaseline..maxAllowedBaseline) THEN {
innerCount: INTEGER ← count; -- integer to eliminate some bounds checks
characterCachePtr: LONG POINTER TO BitmapDescriptor ← LOOPHOLE[(@characterCache[0]) - (bc-'\000)*SIZE[BitmapDescriptor]];
cachedCharPtr: LONG POINTER TO BitmapDescriptor;
For the inner loop, xPosition points to the left edge of the character instead of the baseline to make the per-character BitBlt table setup a little faster.
xPosition.sv.integerPart ← xPosition.sv.integerPart+characterCache.xmin;
IF NOT innerlooping THEN innerCount ← -1 ELSE
WHILE (innerCount ← innerCount - 1) >= 0
AND charPtr^ IN [bc..ec]
AND (bb.src.word ← LOOPHOLE[(cachedCharPtr ← characterCachePtr+Inline.BITSHIFT[(charPtr^ - FIRST[CHAR]), 2])^.refBits]) # NIL
AND xPosition.sv.integerPart IN [leftPixel..rightPixel-LOOPHOLE[bb.width ← cachedCharPtr.widthAndHeight.width, NAT]) DO
IF SIZE[BitmapDescriptor] # 4 THEN ERROR; -- fix the BITSHIFT in the above test
bb.height ← cachedCharPtr.widthAndHeight.height;
bb.dst.word ← pointerBase + xPosition.wordOffset;
bb.dst.bit ← xPosition.bit;
IF blting THEN BitBlt.BITBLT[bb];
xPosition.sv ← xPosition.sv.PLUS[deltaXptr^];
deltaXptr ← deltaXptr + deltaXincrement;
charPtr ← charPtr + charIncrement;
ENDLOOP;
count ← innerCount + 1;
xPosition.sv.integerPart ← xPosition.sv.integerPart-characterCache.xmin;
};
IF count>0
THEN {
IF charPtr^
IN [bc..ec]
THEN {
IF characterCache[charPtr^-bc].refBits =
NIL AND positioning = rounded
AND font.bitmapCacheable
THEN
EnterCharInCache[font, charPtr^];
SetCP[positioning];
IF positioning = exact
OR characterCache[charPtr^-bc].refBits =
NIL
THEN
font.fontGraphicsClass.drawCharProc[font, charPtr^, context]
ELSE {
bitmapDescriptor: BitmapDescriptor ← characterCache[charPtr^-bc];
mark: Graphics.Mark ← Graphics.Save[context];
IF Graphics.SetPaintMode[context, transparent] = invert
THEN
[] ← Graphics.SetPaintMode[context, invert];
CGPrivate.DrawBits[
self: context,
base: LOOPHOLE[bitmapDescriptor.refBits, LONG POINTER],
raster: characterCache.rasterBits/Environment.bitsPerWord,
bitsPerPixel: 0,
x: 0, y: 0,
w: bitmapDescriptor.widthAndHeight.width,
h: bitmapDescriptor.widthAndHeight.height,
xorigin:-characterCache.xmin,
yorigin:-characterCache.ymin
];
Graphics.Restore[context, mark];
};
};
xPosition.sv ← xPosition.sv.PLUS[deltaXptr^];
deltaXptr ← deltaXptr + deltaXincrement;
yPosition.sv ← yPosition.sv.
PLUS[deltaYptr^];
subtract because y is increasing downwards in device space
deltaYptr ← deltaYptr + deltaYincrement;
charPtr ← charPtr + charIncrement;
count ← count - 1;
};
ENDLOOP;
SetCP[exact];
}
ELSE GOTO Oops;
EXITS Oops => SureDrawCharSeq[font, context, count, charPtr, charIncrement, deltaXptr, deltaXincrement, deltaYptr, deltaYincrement, positioning];
};
CharacterCache: TYPE = REF CharacterCacheRec;
CharacterCacheRec:
TYPE =
RECORD [
xmin, ymin, xmax, ymax:
INTEGER,
Font bounding box, in device units (pixels) and coordinates (y increasing downwards)
rasterBits:
CARDINAL,
Number of bits per scanline (always a multiple of Environment.bitsPerWord)
seq: SEQUENCE length: NAT OF BitmapDescriptor
];
WidthAndHeight: TYPE = MACHINE DEPENDENT RECORD [width, height: CARDINAL];
BitmapDescriptor:
TYPE =
RECORD [
refBits:
REF,
Ref to the actual bits
widthAndHeight: WidthAndHeight
For trimming the number of bits that need BLTing
];
GetCharacterCache:
PROCEDURE [font:
FONT]
RETURNS [characterCache: CharacterCache] = {
characterCache ← NARROW[font.characterCache];
IF characterCache =
NIL
THEN {
font.characterCache ← characterCache ← pZone.NEW[CharacterCacheRec[font.ec-font.bc+1]];
characterCache.xmin ← Floor[font.fontBoundingBox.xmin];
characterCache.xmax ← Ceiling[font.fontBoundingBox.xmax];
characterCache.ymin ← Floor[-font.fontBoundingBox.ymax];
characterCache.ymax ← Ceiling[-font.fontBoundingBox.ymin];
characterCache.rasterBits ← ((characterCache.xmax-characterCache.xmin + Environment.bitsPerWord-1) / Environment.bitsPerWord) * Environment.bitsPerWord;
};
};
EnterCharInCache:
PROCEDURE [font:
FONT, char:
CHAR] = {
characterCache: CharacterCache ← NARROW[font.characterCache];
IF characterCache #
NIL
AND char
IN [font.bc..font.ec]
THEN {
context: Graphics.Context;
bitmap: REF GraphicsOps.BitmapRep;
[context, bitmap] ← GetScratchContext[characterCache];
context.SetCP[-characterCache.xmin, characterCache.ymax];
IF char IN [font.bc..font.ec] THEN font.fontGraphicsClass.drawCharProc[font, char, context];
characterCache[char-font.bc] ← [
widthAndHeight: [width: WidthOf[bitmap], height: HeightOf[bitmap]],
refBits: bitmap.base
];
bitmap.base ← NIL;
ReleaseScratchContext[context, bitmap];
};
};
WidthOf:
PROCEDURE [bitmap: GraphicsOps.BitmapRef]
RETURNS [width:
CARDINAL] =
TRUSTED {
bit: LONG DESCRIPTOR FOR PACKED ARRAY CARDINAL OF [0..1] ← DESCRIPTOR[LOOPHOLE[bitmap.base, LONG POINTER], bitmap.raster * Environment.bitsPerWord * bitmap.height];
Bit: PROC [row, col: CARDINAL] RETURNS [[0..1]] = TRUSTED INLINE {RETURN[bit[row*bitmap.raster*Environment.bitsPerWord + col]]};
ColumnAllZero:
PROC [col:
CARDINAL]
RETURNS [
BOOLEAN] =
TRUSTED {
FOR i:
CARDINAL
IN [0..bitmap.height)
DO
IF Bit[i, col] = 1 THEN RETURN [FALSE]
ENDLOOP;
RETURN [TRUE];
};
width ← bitmap.width;
IF trimmingWidth
THEN
WHILE width > 0 AND ColumnAllZero[width-1] DO width ← width-1 ENDLOOP;
};
HeightOf:
PROCEDURE [bitmap: GraphicsOps.BitmapRef]
RETURNS [height:
CARDINAL] =
TRUSTED {
bit: LONG DESCRIPTOR FOR PACKED ARRAY CARDINAL OF [0..1] ← DESCRIPTOR[LOOPHOLE[bitmap.base, LONG POINTER], bitmap.raster * Environment.bitsPerWord * bitmap.height];
Bit: PROC [row, col: CARDINAL] RETURNS [[0..1]] = TRUSTED INLINE {RETURN[bit[row*bitmap.raster*Environment.bitsPerWord + col]]};
RowAllZero:
PROC [row:
CARDINAL]
RETURNS [
BOOLEAN] =
TRUSTED {
FOR j:
CARDINAL
IN [0..bitmap.width)
DO
IF Bit[row, j] = 1 THEN RETURN [FALSE]
ENDLOOP;
RETURN [TRUE];
};
height ← bitmap.height;
IF trimmingHeight
THEN
WHILE height > 0 AND RowAllZero[height-1] DO height ← height-1 ENDLOOP;
};
scratchBitmapRef: REF GraphicsOps.BitmapRep ← NIL;
scratchContext: Graphics.Context ← NIL;
GetScratchContext:
ENTRY
PROCEDURE [characterCache: CharacterCache]
RETURNS [context: Graphics.Context, bitmap:
REF GraphicsOps.BitmapRep] = {
ENABLE UNWIND => NULL;
Words: TYPE = RECORD[SEQUENCE COMPUTED CARDINAL OF WORD];
IF scratchBitmapRef = NIL THEN scratchBitmapRef ← pZone.NEW[GraphicsOps.BitmapRep];
scratchBitmapRef.raster ← characterCache.rasterBits/Environment.bitsPerWord;
scratchBitmapRef.height ← characterCache.ymax-characterCache.ymin;
scratchBitmapRef.width ← characterCache.xmax-characterCache.xmin;
scratchBitmapRef.base ← pZone.NEW[Words[scratchBitmapRef.raster*scratchBitmapRef.height]];
IF scratchContext=NIL THEN scratchContext ← GraphicsOps.NewContextFromBitmap[scratchBitmapRef]
ELSE GraphicsOps.SetTargetBitmap[scratchContext, scratchBitmapRef];
context ← scratchContext; scratchContext ← NIL;
bitmap ← scratchBitmapRef; scratchBitmapRef ← NIL;
};
ReleaseScratchContext:
ENTRY
PROCEDURE [context: Graphics.Context, bitmap:
REF GraphicsOps.BitmapRep] = {
scratchContext ← context;
scratchBitmapRef ← bitmap;
bitmap.base ← NIL;
};
SureDrawCharSeq:
PROCEDURE [
font: FONT, context: Context, count: NAT,
charPtr: LONG POINTER TO CHAR, charIncrement: NAT,
deltaXptr: LONG POINTER TO FixedUnit, deltaXincrement: NAT,
deltaYptr: LONG POINTER TO FixedUnit, deltaYincrement: NAT,
positioning: Positioning ← rounded
] = TRUSTED {
totw: INT ← 0;
positionX: Scaled.Value ← Scaled.FromReal[context.GetCP[].x];
positionY: Scaled.Value ← Scaled.FromReal[context.GetCP[].y];
WHILE count#0
DO
IF positioning=rounded THEN {x, y: REAL; [x,y] ← context.GetCP[rounded: TRUE]; context.SetCP[x,y]};
IF charPtr^ IN [font.bc..font.ec] THEN font.fontGraphicsClass.drawCharProc[font, charPtr^, context];
positionX ← positionX.PLUS[deltaXptr^];
positionY ← positionY.PLUS[deltaYptr^];
context.SetCP[positionX.Float[], positionY.Float[]];
charPtr ← charPtr + charIncrement;
deltaXptr ← deltaXptr + deltaXincrement;
deltaYptr ← deltaYptr + deltaYincrement;
count ← count - 1;
ENDLOOP;
};
}.