DIRECTORY Basics USING [bytesPerWord], BasicTime USING [GMT, nullGMT, Period], FS USING [binaryStreamOptions, EnumerateForNames, GetInfo, Open, OpenFile, StreamBufferParms, StreamFromOpenFile], Imager USING [ConcatT, Context, Error, MaskBitmap, MaskFill, MaskRectangle], ImagerBackdoor USING [GetTransformation], ImagerBox USING [BoundingBox, Box, BoxFromExtents, BoxFromRectangle, Extents, ExtentsFromBox, RectangleFromBox], ImagerFont USING [CorrectionType, nullXChar, XChar], ImagerPath USING [CurveToProc, LineToProc, MoveToProc, PathProc], ImagerSample USING [Box, GetBase, ObtainScratchMap, RasterSampleMap, ReleaseScratchMap, SampleMap], ImagerTransformation USING [ApplyPreRotate, Concat, Destroy, Factor, FactoredTransformation, Rotate, Scale2, SingularValues, Transformation, TransformRectangle, TransformVec], ImagerTypeface USING [Creator, CreatorRep, RegisterCreator, Typeface, TypefaceClass, TypefaceClassRep, TypefaceRep], IO USING [STREAM], PrePressFontFormat USING [CardFromBcpl, CharacterData, CharacterIndexEntry, IntFromBcpl, IXHeader, missingCharacter, missingFilePos, missingSpline, NameIndexEntry, RasterDefn, RelFilePos, SplineCode, SplineCommand, SplineCoords, SplineData, StdIndexEntry], Real USING [FScale], RealConvert USING [BcplToIeee], Rope USING [Cat, Compare, Equal, Fetch, FromProc, Flatten, Index, Match, Replace, ROPE, Run, Size, Substr, Translate, TranslatorType, UnsafeMoveChars], RopeFile USING [FromStream], SystemNames USING [ReleaseName], Vector2 USING [Add, Div, VEC]; ImagerPressfontTypefaceImpl: CEDAR PROGRAM IMPORTS BasicTime, FS, Imager, ImagerBackdoor, ImagerBox, ImagerSample, ImagerTransformation, ImagerTypeface, PrePressFontFormat, Real, RealConvert, Rope, RopeFile, SystemNames, Vector2 ~ BEGIN VEC: TYPE ~ Vector2.VEC; ROPE: TYPE ~ Rope.ROPE; Extents: TYPE ~ ImagerBox.Extents; Transformation: TYPE ~ ImagerTransformation.Transformation; Typeface: TYPE ~ ImagerTypeface.Typeface; XChar: TYPE ~ ImagerFont.XChar; nullXChar: XChar ~ ImagerFont.nullXChar; substituteChar: XChar ~ [set: 360B, code: 312B]; Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD [ setTable: SetTable _ NIL, amplifySpace: BOOL _ FALSE, groups: LIST OF GroupData _ NIL ]; CharSet: TYPE ~ BYTE; SetTable: TYPE ~ REF SetTableRep; SetTableRep: TYPE ~ ARRAY CharSet OF CharTable _ ALL[NIL]; CharTable: TYPE ~ REF CharTableRep; CharTableRep: TYPE ~ RECORD [bc, ec: BYTE, s: SEQUENCE size: NAT OF CharMetrics]; CharMetrics: TYPE ~ REF CharMetricsRep; CharMetricsRep: TYPE ~ RECORD [escapement: VEC, bb: ImagerBox.Box]; Representation: TYPE ~ { spline, alignedRaster, packedRaster }; GroupData: TYPE ~ REF GroupDataRep; GroupDataRep: TYPE ~ RECORD [ base: ROPE _ NIL, set: BYTE _ 0, bc, ec: BYTE _ 0, representation: Representation, dataByteOffset: INT _ 0, directoryByteOffset: INT _ 0, dataByteLength: INT _ 0, pixelToChar: Transformation _ NIL ]; MalformedCDFont: ERROR ~ CODE; Assert: PROC [truth: BOOL] ~ INLINE { IF NOT truth THEN ERROR MalformedCDFont }; Bytes: PROC [wordSize: NAT] RETURNS [CARDINAL] ~ INLINE {RETURN [wordSize*2]}; check: [2..2] ~ Basics.bytesPerWord; FileBytes: PROC [fileWords: INT] RETURNS [INT] ~ INLINE {RETURN [fileWords*2]}; RawFetch: UNSAFE PROC [base: ROPE, byteOffset: INT, destination: LONG POINTER, nBytes: NAT] ~ UNCHECKED { bytesMoved: INT ~ Rope.UnsafeMoveChars[block: [base: destination, startIndex: 0, count: nBytes], rope: base, start: byteOffset]; Assert[bytesMoved = nBytes]; }; streamBufferParms: FS.StreamBufferParms _ [vmPagesPerBuffer: 8, nBuffers: 1]; ropeFileBufferSize: INT _ 1024; ropeFileBuffers: INT _ 8; RopeFromFile: PROC [file: FS.OpenFile] RETURNS [ROPE] ~ { stream: IO.STREAM ~ FS.StreamFromOpenFile[openFile: file, streamOptions: FS.binaryStreamOptions, streamBufferParms: streamBufferParms]; rope: ROPE ~ RopeFile.FromStream[stream: stream, bufSize: ropeFileBufferSize, buffers: ropeFileBuffers]; RETURN [rope] }; CDCreate: PROC [file: FS.OpenFile] RETURNS [Typeface] ~ { base: ROPE ~ RopeFromFile[file]; RETURN [TypefaceFromRopes[LIST[base]]] }; checkForBogusResolution: BOOL _ TRUE; -- There seem to be print services fonts in circulation that have a smashed resolutionX field; if this bool is true, we do some paranoid checks and smash it to 300bpi if it smells funny. tryForCharSet: BOOL _ TRUE; -- The resolutionY field has been re-used in print services fonts to represent the character set; if this bool is true, we use this interpretation after making some reasonableness checks. As long as the resolution is 256 or greater, we can always disambiguate. NameTable: TYPE ~ REF NameTableRep; NameTableRep: TYPE ~ RECORD [SEQUENCE n: NAT OF ROPE]; DecodedFamily: TYPE ~ RECORD [ familySansCharSet: ROPE _ NIL, hasCharSet: BOOL _ FALSE, charSet: BYTE _ 0 ]; DecodeFamily: PROC [family: ROPE] RETURNS [DecodedFamily] ~ { size: NAT ~ Rope.Size[family]; charSet: BYTE _ 0; hasCharSet: BOOL _ FALSE; Assert[family # NIL]; IF size >= 3 THEN { hasCharSet _ TRUE; FOR i: INT IN [size-3 .. size) DO c: CHAR ~ Rope.Fetch[family, i]; IF c IN ['0..'7] THEN {charSet _ charSet*8 + (c-'0)} ELSE {hasCharSet _ FALSE; charSet _ 0; EXIT}; ENDLOOP; }; RETURN [[ familySansCharSet: IF hasCharSet THEN Rope.Substr[family, 0, size-3] ELSE family, hasCharSet: hasCharSet, charSet: charSet ]] }; TypefaceFromRopes: PROC [contents: LIST OF ROPE] RETURNS [Typeface] ~ { data: Data ~ NEW[DataRep _ []]; ix: PrePressFontFormat.IXHeader; indexFace: BYTE; indexFamilySansCharSet: ROPE _ NIL; groups: LIST OF GroupData _ NIL; ValidateFamilyAndFace: PROC [newFace: BYTE, newFamily: ROPE] ~ { IF indexFamilySansCharSet = NIL THEN { indexFamilySansCharSet _ newFamily; indexFace _ newFace; RETURN; }; Assert[indexFace = newFace]; Assert[Rope.Equal[newFamily, indexFamilySansCharSet, FALSE]]; }; FOR each: LIST OF ROPE _ contents, each.rest UNTIL each = NIL DO base: ROPE ~ each.first; byteOffset: INT _ 0; nameTable: NameTable _ NIL; maxNameCode: CARDINAL _ 0; DO -- read the index part to find max name code headerBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.IXHeader]]; TRUSTED { RawFetch[base, byteOffset, @ix, headerBytes] }; SELECT ix.type FROM end => EXIT; name => { name: PrePressFontFormat.NameIndexEntry; nameBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.NameIndexEntry]]; TRUSTED { RawFetch[base, byteOffset+headerBytes, @name, nameBytes] }; maxNameCode _ MAX[maxNameCode, name.code]; }; ENDCASE => NULL; byteOffset _ byteOffset + FileBytes[ix.length]; ENDLOOP; nameTable _ NEW[NameTableRep[maxNameCode+1]]; byteOffset _ 0; DO -- read the index part to get the name codes headerBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.IXHeader]]; TRUSTED { RawFetch[base, byteOffset, @ix, headerBytes] }; SELECT ix.type FROM end => EXIT; name => { name: PrePressFontFormat.NameIndexEntry; nameBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.NameIndexEntry]]; i: NAT _ 0; p: PROC RETURNS [CHAR] ~ {RETURN [VAL[name.chars[i _ i + 1]]]}; TRUSTED { RawFetch[base, byteOffset+headerBytes, @name, nameBytes] }; nameTable[name.code] _ Rope.FromProc[len: name.chars[0], p: p]; }; ENDCASE => NULL; byteOffset _ byteOffset + FileBytes[ix.length]; ENDLOOP; byteOffset _ 0; DO -- read the index part headerBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.IXHeader]]; TRUSTED { RawFetch[base, byteOffset, @ix, headerBytes] }; SELECT ix.type FROM end => EXIT; name => NULL; spline => { index: PrePressFontFormat.StdIndexEntry; sdixBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.StdIndexEntry]]; group: GroupData ~ NEW[GroupDataRep]; segmentByteIndex, segmentBytes: INT _ 0; decodedFamily: DecodedFamily _ []; group.representation _ spline; group.set _ 0; Assert[FileBytes[ix.length] = headerBytes + sdixBytes]; TRUSTED { RawFetch[base, byteOffset+headerBytes, @index, sdixBytes] }; Assert[index.bc <= index.ec]; group.base _ base; group.bc _ index.bc; group.ec _ index.ec; group.dataByteOffset _ FileBytes[PrePressFontFormat.CardFromBcpl[index.segmentSA]]; group.dataByteLength _ FileBytes[PrePressFontFormat.CardFromBcpl[index.segmentLength]]; Assert[group.dataByteOffset + group.dataByteLength <= Rope.Size[base]]; group.directoryByteOffset _ group.dataByteOffset + (index.ec-index.bc+1) * Bytes[SIZE[PrePressFontFormat.SplineData]]; group.pixelToChar _ ImagerTransformation.Rotate[-index.rotation/60.0]; decodedFamily _ DecodeFamily[nameTable[index.family]]; ValidateFamilyAndFace[newFace: index.face, newFamily: decodedFamily.familySansCharSet]; group.set _ decodedFamily.charSet; { splineDataEntryBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.SplineData]]; dirEntryBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.RelFilePos]]; len: NAT ~ (group.ec-group.bc+1) * (splineDataEntryBytes+dirEntryBytes); group.base _ Rope.Replace[base: base, start: group.dataByteOffset, len: len, with: Rope.Flatten[base: base, start: group.dataByteOffset, len: len]]; }; groups _ CONS[group, groups]; }; character => { index: PrePressFontFormat.CharacterIndexEntry; cixBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.CharacterIndexEntry]]; group: GroupData ~ NEW[GroupDataRep]; charUnitsPerResolutionUnit: REAL _ 0; decodedFamily: DecodedFamily _ []; group.representation _ alignedRaster; group.set _ 0; Assert[FileBytes[ix.length] = headerBytes + cixBytes]; TRUSTED { RawFetch[base, byteOffset+headerBytes, @index, cixBytes] }; IF checkForBogusResolution THEN { IF index.resolutionX = index.resolutionY OR index.resolutionX MOD 1500 = 0 OR (SELECT index.resolutionX FROM 3840, 2000, 2540, 2400, 720 => TRUE ENDCASE => FALSE) THEN NULL ELSE index.resolutionX _ 3000 }; IF tryForCharSet THEN { IF index.resolutionY MOD 10 = 0 AND index.resolutionY/10 IN CharSet THEN { group.set _ index.resolutionY/10; index.resolutionY _ index.resolutionX; }; }; Assert[index.bc <= index.ec]; group.base _ base; group.bc _ index.bc; group.ec _ index.ec; group.dataByteOffset _ FileBytes[PrePressFontFormat.CardFromBcpl[index.segmentSA]]; group.dataByteLength _ FileBytes[PrePressFontFormat.CardFromBcpl[index.segmentLength]]; Assert[group.dataByteOffset + group.dataByteLength <= Rope.Size[base]]; group.directoryByteOffset _ group.dataByteOffset + (index.ec-index.bc+1) * Bytes[SIZE[PrePressFontFormat.CharacterData]]; charUnitsPerResolutionUnit _ 25400.0/index.size; -- units of resolution are dots per 10 inches group.pixelToChar _ ImagerTransformation.Scale2[[ charUnitsPerResolutionUnit/index.resolutionX, charUnitsPerResolutionUnit/index.resolutionY ]]; IF index.rotation#0 THEN group.pixelToChar.ApplyPreRotate[-index.rotation/60.0]; decodedFamily _ DecodeFamily[nameTable[index.family]]; ValidateFamilyAndFace[newFace: index.face, newFamily: decodedFamily.familySansCharSet]; IF decodedFamily.hasCharSet THEN group.set _ decodedFamily.charSet; { charDataEntryBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.CharacterData]]; dirEntryBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.RelFilePos]]; len: NAT ~ (group.ec-group.bc+1) * (charDataEntryBytes+dirEntryBytes); group.base _ Rope.Replace[base: base, start: group.dataByteOffset, len: len, with: Rope.Flatten[base: base, start: group.dataByteOffset, len: len]]; }; groups _ CONS[group, groups]; }; ENDCASE => ERROR MalformedCDFont; -- unexpected ix type byteOffset _ byteOffset + FileBytes[ix.length]; ENDLOOP; ENDLOOP; Assert[indexFamilySansCharSet # NIL]; data.groups _ SortGroups[groups]; data.setTable _ BuildSetTable[data.groups]; data.amplifySpace _ indexFace < 18; -- 40B is an ordinary character in TEX fonts RETURN[NEW[ImagerTypeface.TypefaceRep _ [class: cdClass, data: data]]]; }; InOrder: PROC [a, b: GroupData] RETURNS [BOOL] ~ { SELECT TRUE FROM a.set # b.set => RETURN [a.set < b.set]; a.representation # b.representation AND (a.representation=spline OR b.representation=spline) => RETURN [a.representation < b.representation]; ENDCASE => { sva: VEC ~ ImagerTransformation.SingularValues[a.pixelToChar]; svb: VEC ~ ImagerTransformation.SingularValues[b.pixelToChar]; IF sva.x < svb.x THEN RETURN [TRUE]; IF sva.x = svb.x AND sva.y < svb.y THEN RETURN [TRUE]; RETURN [FALSE]; }; }; SortGroups: PROC [groups: LIST OF GroupData] RETURNS [LIST OF GroupData] ~ { unconsumed: LIST OF GroupData _ groups; new: LIST OF GroupData _ NIL; WHILE unconsumed # NIL DO rest: LIST OF GroupData ~ unconsumed.rest; current: GroupData ~ unconsumed.first; a: LIST OF GroupData _ new; p: LIST OF GroupData _ NIL; WHILE a#NIL AND NOT InOrder[a.first, current] DO p _ a; a _ a.rest ENDLOOP; IF p = NIL THEN { unconsumed.rest _ new; new _ unconsumed } ELSE { unconsumed.rest _ p.rest; p.rest _ unconsumed }; unconsumed _ rest; ENDLOOP; RETURN [new]; }; BuildSetTable: PROC [groups: LIST OF GroupData] RETURNS [SetTable] ~ { setTable: SetTable ~ NEW[SetTableRep _ ALL[NIL]]; setbc: REF PACKED ARRAY CharSet OF BYTE _ NEW[PACKED ARRAY CharSet OF BYTE _ ALL[LAST[BYTE]]]; setec: REF PACKED ARRAY CharSet OF BYTE _ NEW[PACKED ARRAY CharSet OF BYTE _ ALL[FIRST[BYTE]]]; FOR each: LIST OF GroupData _ groups, each.rest UNTIL each = NIL DO group: GroupData ~ each.first; set: BYTE ~ group.set; setbc^[set] _ MIN[group.bc, setbc^[set]]; setec^[set] _ MAX[group.ec, setec^[set]]; ENDLOOP; FOR set: CharSet IN CharSet DO IF setbc[set] <= setec[set] THEN { charTable: CharTable ~ NEW[CharTableRep[setec[set]-setbc[set]+1]]; charTable.bc _ setbc^[set]; charTable.ec _ setec^[set]; FOR i: NAT IN [0..charTable.size) DO charTable[i] _ NIL ENDLOOP; setTable[set] _ charTable; }; ENDLOOP; FOR each: LIST OF GroupData _ groups, each.rest UNTIL each = NIL DO group: GroupData ~ each.first; charTable: CharTable ~ setTable[group.set]; FOR code: BYTE IN [group.bc..group.ec] DO SELECT group.representation FROM spline => { sd: PrePressFontFormat.SplineData ~ GetSplineData[group, code]; IF sd.wx#PrePressFontFormat.missingSpline THEN { wx: REAL ~ RealConvert.BcplToIeee[sd.wx]; wy: REAL ~ RealConvert.BcplToIeee[sd.wy]; e: VEC ~ ImagerTransformation.TransformVec[group.pixelToChar, [wx, wy]]; wbb: ImagerBox.Box ~ [xmin: RealConvert.BcplToIeee[sd.xmin], ymin: RealConvert.BcplToIeee[sd.ymin], xmax: RealConvert.BcplToIeee[sd.xmax], ymax: RealConvert.BcplToIeee[sd.ymax]]; box: ImagerBox.Box ~ TransformBox[group.pixelToChar, wbb]; charMetrics: CharMetrics ~ charTable[code-charTable.bc]; IF charMetrics = NIL THEN charTable[code-charTable.bc] _ NEW [CharMetricsRep _ [escapement: e, bb: box]] ELSE charMetrics.bb _ ImagerBox.BoundingBox[charMetrics.bb, box]; }; }; alignedRaster => { cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, code]; IF cd.bbdy#PrePressFontFormat.missingCharacter THEN { wx: REAL ~ Real.FScale[PrePressFontFormat.IntFromBcpl[cd.wx], -16]; wy: REAL ~ Real.FScale[PrePressFontFormat.IntFromBcpl[cd.wy], -16]; e: VEC ~ ImagerTransformation.TransformVec[group.pixelToChar, [wx, wy]]; box: ImagerBox.Box ~ ImagerBox.BoxFromRectangle[ImagerTransformation.TransformRectangle[group.pixelToChar, [x: cd.bbox, y: cd.bboy, w: cd.bbdx, h: cd.bbdy]]]; charMetrics: CharMetrics ~ charTable[code-charTable.bc]; IF charMetrics = NIL THEN charTable[code-charTable.bc] _ NEW [CharMetricsRep _ [escapement: e, bb: box]] ELSE charMetrics.bb _ ImagerBox.BoundingBox[charMetrics.bb, box]; }; }; ENDCASE => ERROR; ENDLOOP; ENDLOOP; setbc _ NIL; setec _ NIL; -- Local use only; an explicit FREE here would work RETURN [setTable] }; TransformBox: PROC [m: Transformation, box: ImagerBox.Box] RETURNS [ImagerBox.Box] ~ { RETURN [ImagerBox.BoxFromRectangle[ImagerTransformation.TransformRectangle[m, ImagerBox.RectangleFromBox[box]]]] }; GetCharacterData: PROC [group: GroupData, code: BYTE] RETURNS [PrePressFontFormat.CharacterData] ~ { characterData: PrePressFontFormat.CharacterData; cdBytes: INT ~ Bytes[SIZE[PrePressFontFormat.CharacterData]]; IF group.representation # alignedRaster THEN ERROR; characterData.bbdy _ PrePressFontFormat.missingCharacter; IF code IN [group.bc..group.ec] THEN TRUSTED { RawFetch[group.base, group.dataByteOffset + (code-group.bc) * cdBytes, @characterData, cdBytes] }; RETURN [characterData]; }; GetSplineData: PROC [group: GroupData, code: BYTE] RETURNS [PrePressFontFormat.SplineData] ~ { splineData: PrePressFontFormat.SplineData; splineDataBytes: INT ~ Bytes[SIZE[PrePressFontFormat.SplineData]]; IF group.representation # spline THEN ERROR; splineData.wx _ PrePressFontFormat.missingSpline; IF code IN [group.bc..group.ec] THEN TRUSTED { RawFetch[group.base, group.dataByteOffset + (code-group.bc) * splineDataBytes, @splineData, splineDataBytes] }; RETURN [splineData]; }; CDContains: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [BOOL] ~ { data: Data ~ NARROW[self.data]; charTable: CharTable ~ data.setTable[char.set]; IF charTable = NIL OR char.code NOT IN [charTable.bc..charTable.ec] THEN RETURN [FALSE]; RETURN [charTable[char.code-charTable.bc] # NIL]; }; CDNextChar: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [ImagerFont.XChar] ~ { data: Data ~ NARROW[self.data]; next: XChar _ IF char = nullXChar THEN [0, 0] ELSE IF char.code = LAST[BYTE] THEN [set: char.set+1, code: FIRST[BYTE]] ELSE [set: char.set, code: char.code+1]; charTable: CharTable _ data.setTable[next.set]; UNTIL next = nullXChar OR (charTable # NIL AND next.code IN [charTable.bc..charTable.ec] AND charTable[next.code-charTable.bc] # NIL) DO SELECT TRUE FROM charTable = NIL OR next.code > charTable.ec OR next.code = LAST[BYTE] => { next _ IF next.set = nullXChar.set THEN nullXChar ELSE [next.set+1, 0]; charTable _ data.setTable[next.set]; }; next.code < charTable.bc => next.code _ charTable.bc; ENDCASE => next.code _ next.code + 1; ENDLOOP; RETURN [next]; }; CDEscapement: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [VEC] ~ { data: Data ~ NARROW[self.data]; charTable: CharTable ~ data.setTable[char.set]; IF charTable # NIL AND char.code IN [charTable.bc..charTable.ec] THEN { charMetrics: CharMetrics ~ charTable[char.code-charTable.bc]; IF charMetrics # NIL THEN RETURN [charMetrics.escapement] }; RETURN[[0.5, 0]]; }; CDAmplified: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [BOOL] ~ { data: Data ~ NARROW[self.data]; RETURN [data.amplifySpace AND char = [set: 0, code: 40B]]; }; CDCorrection: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [ImagerFont.CorrectionType] ~ { data: Data ~ NARROW[self.data]; IF CDContains[self, char] THEN { IF data.amplifySpace AND char = [set: 0, code: 40B] THEN RETURN[space]; RETURN[mask]; }; RETURN[none]; }; CDBoundingBox: PROC [self: Typeface, char: ImagerFont.XChar] RETURNS [Extents] ~ { data: Data ~ NARROW[self.data]; charTable: CharTable ~ data.setTable[char.set]; IF charTable # NIL AND char.code IN [charTable.bc..charTable.ec] THEN { charMetrics: CharMetrics ~ charTable[char.code-charTable.bc]; IF charMetrics # NIL THEN RETURN [ImagerBox.ExtentsFromBox[charMetrics.bb]]; }; IF char # substituteChar THEN RETURN [CDBoundingBox[self, substituteChar]]; RETURN[[leftExtent: -0.05, rightExtent: 0.45, descent: 0, ascent: 0.6]]; }; CDFontBoundingBox: PROC [self: Typeface] RETURNS [Extents] ~ { data: Data ~ NARROW[self.data]; setTable: SetTable ~ data.setTable; bb: ImagerBox.Box _ ImagerBox.BoxFromExtents[CDBoundingBox[self, substituteChar]]; FOR set: CharSet IN CharSet DO charTable: CharTable ~ setTable[set]; IF charTable # NIL THEN { FOR code: BYTE IN [charTable.bc..charTable.ec] DO charMetrics: CharMetrics ~ charTable[code-charTable.bc]; IF charMetrics # NIL THEN { b: ImagerBox.Box ~ charMetrics.bb; bb _ [xmin: MIN[bb.xmin, b.xmin], ymin: MIN[bb.ymin, b.ymin], xmax: MAX[bb.xmax, b.xmax], ymax: MAX[bb.ymax, b.ymax]] }; ENDLOOP; }; ENDLOOP; RETURN [ImagerBox.ExtentsFromBox[bb]] }; CDKern: PROC [self: Typeface, char, successor: ImagerFont.XChar] RETURNS [VEC] ~ { RETURN[[0, 0]] }; CDNextKern: PROC [self: Typeface, char, successor: ImagerFont.XChar] RETURNS [ImagerFont.XChar] ~ { RETURN[ImagerFont.nullXChar] }; CDLigature: PROC [self: Typeface, char, successor: ImagerFont.XChar] RETURNS [ImagerFont.XChar] ~ { RETURN[ImagerFont.nullXChar] }; CDNextLigature: PROC [self: Typeface, char, successor: ImagerFont.XChar] RETURNS [ImagerFont.XChar] ~ { RETURN[ImagerFont.nullXChar] }; GetBitmap: PROC [group: GroupData, code: BYTE] RETURNS [ImagerSample.RasterSampleMap] ~ { sampleMap: ImagerSample.RasterSampleMap _ NIL; cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, code]; IF cd.bbdy#PrePressFontFormat.missingCharacter THEN TRUSTED { rfpBytes: INT ~ Bytes[SIZE[PrePressFontFormat.RelFilePos]]; rfpBytePos: INT ~ group.directoryByteOffset+(code-group.bc)*rfpBytes; rfp: PrePressFontFormat.RelFilePos; TRUSTED {RawFetch[group.base, rfpBytePos, @rfp, rfpBytes] }; IF rfp # PrePressFontFormat.missingFilePos THEN { relativeByte: INT ~ FileBytes[PrePressFontFormat.IntFromBcpl[rfp]]; rdBytes: INT ~ Bytes[SIZE[PrePressFontFormat.RasterDefn]]; rd: PrePressFontFormat.RasterDefn; RawFetch[group.base, group.directoryByteOffset+relativeByte, @rd, rdBytes]; Assert[rd.lines=cd.bbdx AND rd.raster=(cd.bbdy+15)/16]; IF cd.bbdx>0 AND cd.bbdy>0 THEN { rasterBytes: INT ~ FileBytes[LONG[rd.raster]*rd.lines]; sampleMap _ ImagerSample.ObtainScratchMap[ box: [min: [s: cd.bbox, f: cd.bboy], max: [s: cd.bbdx+cd.bbox, f: cd.bbdy+cd.bboy]], bitsPerSample: 1 ]; TRUSTED {RawFetch[group.base, group.directoryByteOffset+relativeByte+rdBytes, ImagerSample.GetBase[sampleMap].word, rasterBytes] }; }; }; }; RETURN [sampleMap]; }; Right: PROC [angle: REAL] RETURNS [BOOL] ~ { WHILE angle < 0 DO angle _ angle + 90.0 ENDLOOP; WHILE angle > 45.0 DO angle _ angle - 90.0 ENDLOOP; RETURN [ABS[angle] <= 1.666667E-2] }; minScale: REAL _ 0.9875; maxScale: REAL _ 1.0125; FindBestGroup: PROC [data: Data, char: ImagerFont.XChar, t: Transformation] RETURNS [GroupData] ~ { best: GroupData _ NIL; FOR each: LIST OF GroupData _ data.groups, each.rest UNTIL each = NIL DO group: GroupData ~ each.first; IF group.set = char.set THEN { SELECT group.representation FROM spline => {best _ group}; alignedRaster => { cd: PrePressFontFormat.CharacterData ~ GetCharacterData[group, char.code]; IF cd.bbdy#PrePressFontFormat.missingCharacter THEN { IF t # NIL THEN { composite: Transformation _ ImagerTransformation.Concat[group.pixelToChar, t]; f: ImagerTransformation.FactoredTransformation ~ ImagerTransformation.Factor[composite]; ImagerTransformation.Destroy[composite]; composite _ NIL; IF Right[f.r1] AND Right[f.r2] AND ABS[f.s.x] IN [minScale..maxScale] AND ABS[f.s.y] IN [minScale..maxScale] THEN {best _ group; EXIT}; }; IF best = NIL THEN best _ group; }; }; ENDCASE => ERROR; }; ENDLOOP; IF best = NIL THEN ERROR; -- CDContains lied! RETURN [best]; }; GetClientToDevice: PROC [context: Imager.Context] RETURNS [t: Transformation _ NIL] ~ { t _ ImagerBackdoor.GetTransformation[context: context, from: client, to: device ! Imager.Error => CONTINUE]; }; CDMask: PROC [self: Typeface, char: ImagerFont.XChar, context: Imager.Context] ~ { data: Data ~ NARROW[self.data]; IF CDContains[self, char] THEN { t: Transformation _ GetClientToDevice[context]; group: GroupData ~ FindBestGroup[data, char, t]; IF t # NIL THEN { ImagerTransformation.Destroy[t]; t _ NIL}; SELECT group.representation FROM spline => MaskSpline[group, char.code, context]; alignedRaster => { bitmap: ImagerSample.SampleMap _ GetBitmap[group, char.code]; IF bitmap # NIL THEN { Imager.ConcatT[context, group.pixelToChar]; Imager.MaskBitmap[context: context, bitmap: bitmap, referencePoint: [0, 0], scanMode: [slow: right, fast: up], position: [0, 0]]; ImagerSample.ReleaseScratchMap[bitmap]; }; }; ENDCASE => ERROR; } ELSE { IF char # substituteChar THEN CDMask[self, substituteChar, context] ELSE Imager.MaskRectangle[context, [0.05, 0, 0.4, 0.6]]; }; }; MaskSpline: PROC [group: GroupData, code: BYTE, context: Imager.Context] ~ { path: ImagerPath.PathProc ~ { MapSplineOutline[group: group, code: code, moveTo: moveTo, lineTo: lineTo, curveTo: curveTo] }; Imager.MaskFill[context: context, path: path, oddWrap: TRUE]; }; MapSplineOutline: PROC [group: GroupData, code: BYTE, moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc] ~ { InitByteIndex: PROC RETURNS [INT] ~ TRUSTED { filePos: PrePressFontFormat.RelFilePos; filePosBytes: INT ~ Bytes[SIZE[PrePressFontFormat.RelFilePos]]; RawFetch[base: group.base, byteOffset: group.directoryByteOffset + (code-group.bc)*filePosBytes, destination: @filePos, nBytes: filePosBytes]; IF filePos=PrePressFontFormat.missingFilePos THEN ERROR MalformedCDFont; RETURN [group.directoryByteOffset + FileBytes[PrePressFontFormat.IntFromBcpl[filePos]]]; }; initByteIndex: INT ~ InitByteIndex[]; byteIndex: INT _ initByteIndex; lp: VEC _ [0, 0]; GetCode: PROC RETURNS [PrePressFontFormat.SplineCode] ~ TRUSTED { sCmd: PrePressFontFormat.SplineCommand; nBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.SplineCommand]]; RawFetch[base: group.base, byteOffset: byteIndex, destination: @sCmd, nBytes: nBytes]; byteIndex _ byteIndex + nBytes; RETURN[sCmd.code]; }; GetCoords: PROC RETURNS [VEC] ~ TRUSTED { sCo: PrePressFontFormat.SplineCoords; nBytes: NAT ~ Bytes[SIZE[PrePressFontFormat.SplineCoords]]; RawFetch[base: group.base, byteOffset: byteIndex, destination: @sCo, nBytes: nBytes]; byteIndex _ byteIndex + nBytes; RETURN[[RealConvert.BcplToIeee[sCo.x], RealConvert.BcplToIeee[sCo.y]]]; }; DO SELECT GetCode[] FROM $moveTo => { p: VEC ~ GetCoords[]; moveTo[p]; lp _ p; }; $drawTo => { p: VEC ~ GetCoords[]; lineTo[p]; lp _ p; }; $drawCurve => { c1: VEC ~ GetCoords[]; c2: VEC ~ GetCoords[]; c3: VEC ~ GetCoords[]; b1: VEC ~ lp.Add[c1.Div[3]]; b2: VEC ~ b1.Add[c1.Add[c2].Div[3]]; b3: VEC ~ lp.Add[c1].Add[c2].Add[c3]; curveTo[b1, b2, b3]; lp _ b3; }; $endDefinition => EXIT; $newObject => ERROR MalformedCDFont; -- not implemented ENDCASE => ERROR MalformedCDFont; -- undefined ENDLOOP; }; cdClass: ImagerTypeface.TypefaceClass ~ NEW [ImagerTypeface.TypefaceClassRep _ [ type: $CD, Contains: CDContains, NextChar: CDNextChar, Escapement: CDEscapement, Amplified: CDAmplified, Correction: CDCorrection, BoundingBox: CDBoundingBox, FontBoundingBox: CDFontBoundingBox, Ligature: CDLigature, NextLigature: CDNextLigature, Kern: CDKern, NextKern: CDNextKern, Mask: CDMask ]]; releaseName: ROPE ~ SystemNames.ReleaseName[]; versionKet: ROPE _ Rope.Cat[releaseName, ">"]; PrefixSizeFromFileName: PROC [fullFName: ROPE] RETURNS [INT] ~ { d: INT _ 0; Strip: PROC [rope: ROPE] RETURNS [BOOL] ~ { size: INT ~ Rope.Size[rope]; IF Rope.Run[s1: fullFName, pos1: d, s2: rope, case: FALSE] = size THEN { d _ d + size; RETURN [TRUE] }; RETURN [FALSE] }; IF NOT Strip["[]<>"] THEN ERROR; WHILE Strip["Fonts>"] OR Strip[versionKet] DO ENDLOOP; RETURN [d]; }; CSVersionFromFileName: PROC [fullFName: ROPE] RETURNS [ROPE] ~ { d1: INT ~ PrefixSizeFromFileName[fullFName]; -- Skip file name prefix d2: INT ~ Rope.Index[fullFName, d1, ">"]+1; -- Skip "Xerox>" d3: INT ~ Rope.Index[fullFName, d2, ">"]; -- Find end of version part RETURN [Rope.Substr[fullFName, d2, d3-d2]]; }; FontNameFromFileName: PROC [fullFName: ROPE] RETURNS [ROPE] ~ { d1: INT ~ PrefixSizeFromFileName[fullFName]; -- Skip file name prefix d2: INT ~ Rope.Index[fullFName, d1+1, "."]; -- Find end of font name translator: Rope.TranslatorType ~ { new _ IF old = '> THEN '/ ELSE old }; rope: ROPE ~ Rope.Translate[base: fullFName, start: d1, len: d2-d1, translator: translator]; RETURN [rope] }; xcFontRootSlashes: ROPE _ Rope.Cat["///", releaseName, "/Fonts/Xerox/"]; XCCreate: PROC [self: ImagerTypeface.Creator, name: ROPE, substitute: BOOL] RETURNS [Typeface] ~ { IF Rope.Match["xerox/xc*/*", name, FALSE] THEN { vStart: INT ~ Rope.Index[name, 0, "/"]+1; vEnd: INT ~ Rope.Index[name, vStart, "/"]; charSetVersion: ROPE ~ Rope.Substr[name, vStart, vEnd-vStart]; shortName: ROPE ~ Rope.Substr[name, vEnd+1]; versionPattern: ROPE ~ IF substitute THEN "xc*" ELSE charSetVersion; pattern: ROPE ~ Rope.Cat[xcFontRootSlashes, versionPattern, "/", shortName, ".cd*!H"]; matches: LIST OF ROPE _ NIL; matchedVersion: ROPE _ NIL; nameProc: PROC [fullFName: ROPE] RETURNS [continue: BOOL _ TRUE] ~ { csVersion: ROPE ~ CSVersionFromFileName[fullFName]; IF NOT Rope.Equal[csVersion, matchedVersion, FALSE] THEN { IF Rope.Compare[matchedVersion, charSetVersion, FALSE] IN [equal..greater] THEN RETURN [continue: FALSE]; matchedVersion _ csVersion; matches _ NIL; }; matches _ CONS[fullFName, matches]; }; FS.EnumerateForNames[pattern: pattern, proc: nameProc]; IF matches # NIL THEN { contents: LIST OF ROPE _ NIL; typeface: Typeface _ NIL; newestCreated: BasicTime.GMT _ BasicTime.nullGMT; FOR each: LIST OF ROPE _ matches, each.rest UNTIL each = NIL DO file: FS.OpenFile ~ FS.Open[each.first]; created: BasicTime.GMT ~ FS.GetInfo[file].created; base: ROPE ~ RopeFromFile[file]; contents _ CONS[base, contents]; IF newestCreated = BasicTime.nullGMT OR BasicTime.Period[from: created, to: newestCreated] < 0 THEN newestCreated _ created; ENDLOOP; typeface _ TypefaceFromRopes[contents]; typeface.name _ FontNameFromFileName[matches.first]; typeface.created _ newestCreated; RETURN [typeface] }; }; RETURN [NIL] }; PressFontCreator: PROC [self: ImagerTypeface.Creator, name: ROPE, substitute: BOOL] RETURNS [Typeface] ~ { IF Rope.Match["xerox/pressfonts/*", name, FALSE] THEN { pattern: ROPE ~ Rope.Cat["///", releaseName, "/Fonts/", name, ".cd*!H"]; matches: LIST OF ROPE _ NIL; nameProc: PROC [fullFName: ROPE] RETURNS [continue: BOOL _ TRUE] ~ { matches _ CONS[fullFName, matches]; }; FS.EnumerateForNames[pattern: pattern, proc: nameProc]; IF matches # NIL THEN { contents: LIST OF ROPE _ NIL; typeface: Typeface _ NIL; newestCreated: BasicTime.GMT _ BasicTime.nullGMT; FOR each: LIST OF ROPE _ matches, each.rest UNTIL each = NIL DO file: FS.OpenFile ~ FS.Open[each.first]; created: BasicTime.GMT ~ FS.GetInfo[file].created; base: ROPE ~ RopeFromFile[file]; contents _ CONS[base, contents]; IF newestCreated = BasicTime.nullGMT OR BasicTime.Period[from: created, to: newestCreated] < 0 THEN newestCreated _ created; ENDLOOP; typeface _ TypefaceFromRopes[contents]; typeface.name _ FontNameFromFileName[matches.first]; typeface.created _ newestCreated; RETURN [typeface] }; }; RETURN [NIL] }; DiagnosticFontCreator: PROC [self: ImagerTypeface.Creator, name: ROPE, substitute: BOOL] RETURNS [Typeface] ~ { IF Rope.Match["xerox/diagnostic/*", name, FALSE] THEN { pattern: ROPE ~ Rope.Cat["///", releaseName, "/Fonts/", name, ".cd*!H"]; matches: LIST OF ROPE _ NIL; nameProc: PROC [fullFName: ROPE] RETURNS [continue: BOOL _ TRUE] ~ { matches _ CONS[fullFName, matches]; }; FS.EnumerateForNames[pattern: pattern, proc: nameProc]; IF matches # NIL THEN { contents: LIST OF ROPE _ NIL; typeface: Typeface _ NIL; newestCreated: BasicTime.GMT _ BasicTime.nullGMT; FOR each: LIST OF ROPE _ matches, each.rest UNTIL each = NIL DO file: FS.OpenFile ~ FS.Open[each.first]; created: BasicTime.GMT ~ FS.GetInfo[file].created; base: ROPE ~ RopeFromFile[file]; contents _ CONS[base, contents]; IF newestCreated = BasicTime.nullGMT OR BasicTime.Period[from: created, to: newestCreated] < 0 THEN newestCreated _ created; ENDLOOP; typeface _ TypefaceFromRopes[contents]; typeface.name _ FontNameFromFileName[matches.first]; typeface.created _ newestCreated; RETURN [typeface] }; }; RETURN [NIL] }; xcCreator: ImagerTypeface.Creator ~ NEW[ImagerTypeface.CreatorRep _ [proc: XCCreate, data: NIL]]; pressFontCreator: ImagerTypeface.Creator ~ NEW[ImagerTypeface.CreatorRep _ [proc: PressFontCreator, data: NIL]]; diagnosticFontCreator: ImagerTypeface.Creator ~ NEW[ImagerTypeface.CreatorRep _ [proc: DiagnosticFontCreator, data: NIL]]; ImagerTypeface.RegisterCreator[pressFontCreator, TRUE]; END.  ImagerPressfontTypefaceImpl.mesa Copyright Σ 1986, 1987 by Xerox Corporation. All rights reserved. Michael Plass, March 23, 1987 2:10:42 pm PST Doug Wyatt, January 24, 1987 9:57:25 pm PST Tim Diebert: April 23, 1987 7:34:43 am PDT Dragon conversion note; the construct Bytes[SIZE[type]] should be changed to work properly for 32-bit machines. We can get by with this here because the press font formats are all based on 16-bit words. FileBytes is used to convert a count of 16-bit words into bytes. This does not change even if the machine's word size is other than 16; This procedure examines a family name for the the presence of character set information. first time through The following makes a flat representation for the directory portion of the rope representation, to avoid having to do IO to get at the metrics. It is strictly an optimization. The following makes a flat representation for the directory portion of the rope representation, to avoid having to do IO to get at the metrics. It is strictly an optimization. bitsPerLine: raster*16, -- this is really 16, not bitsPerWord! t may be NIL, in which case we only try for splines. returns NIL if the context does not have a client-to-device transformation nSplineChars _ nSplineChars + 1; totalSplineBytes _ totalSplineBytes + (byteIndex-initByteIndex); maxSplineBytes _ MAX[maxSplineBytes, (byteIndex-initByteIndex)]; nSplineChars: INT _ 0; totalSplineBytes: INT _ 0; maxSplineBytes: INT _ 0; for example, "///7.0/Fonts/Xerox/" ImagerTypeface.RegisterCreator[xcCreator, TRUE]; ImagerTypeface.RegisterCreator[ImagerTypeface.CreatorFromFileExtension["AC", CDCreate]]; ImagerTypeface.RegisterCreator[ImagerTypeface.CreatorFromFileExtension["CD", CDCreate]]; ImagerTypeface.RegisterCreator[ImagerTypeface.CreatorFromFileExtension["SD", CDCreate]]; Κ$ς˜codešœ ™ KšœB™BKšœ,™,Kšœ+™+K™*K™—šΟk ˜ Kšœœ˜Kšœ œœ˜'Jšœœj˜rKšœœ@˜LKšœœ˜)Kšœ œa˜pKšœ œ$˜4Kšœ œ1˜AKšœ œQ˜cKšœœ•˜―Kšœœ`˜tJšœœœ˜Kšœœθ˜€Kšœœ ˜Kšœ œ˜KšœœHœA˜—Jšœ œ˜Kšœ œ˜ Kšœœ œ˜—K˜KšΠlnœœ˜*Kšœ œ€˜Ήšœ˜K˜Kšœœ œ˜Kšœœœ˜Kšœ œ˜"Kšœœ'˜;Kšœ œ˜)Kšœœ˜Kšœ(˜(šœ0˜0K˜K˜—Kšœœœ ˜šœ œœ˜Kšœœ˜Kšœœœ˜Kšœœœ ˜Kšœ˜K˜—Kšœ œœ˜Kšœ œœ ˜!š œ œœ œ œœ˜:K˜—Kšœ œœ˜#š œœœ œœœœ˜QK˜—Kšœ œœ˜'šœœœœ˜CK˜—Kšœœ+˜?Kšœ œœ˜#šœœœ˜Kšœœœ˜Kšœœ˜Kšœœ˜K˜Kšœœ˜Kšœœ˜Kšœœ˜Kšœ˜!K˜——˜šΟnœœœ˜K˜—šŸœœ œœœœœœ˜PK˜—šŸœœ œœœœœ˜NK˜$KšœΛ™ΛK˜—šŸ œœ œœœœœ˜OKšœLΟbœ9™ˆK˜—šŸœœœœœœœ œ œ˜iK•StartOfExpansionM[buffer: REF TEXT, rope: ROPE, start: INT _ 0, len: INT _ 2147483647]šœ œq˜€Kšœ˜Kšœ˜K˜—Kšœœ8˜MKšœœ˜šœœ˜K˜—š Ÿ œœœ œœ˜9Kš œœœœ3œ<˜‡K–g[stream: STREAM, start: INT _ 0, len: INT _ 2147483647, bufSize: INT _ 512, buffers: NAT _ 4]šœœ^˜hKšœ˜ Kšœ˜K˜—šŸœœœ œ˜9Kšœœ˜ Kšœœ˜&Kšœ˜K˜—KšœœœΟcΊ˜ΰšœœœ‘…˜‘K˜—Kšœ œœ˜#š œœœœœœœ˜6K˜—šœœœ˜Kšœœœ˜Kšœ œœ˜Kšœ œ˜Kšœ˜K˜—šŸ œœ œœ˜=KšœX™XKšœœ˜Kšœ œ˜Kšœ œœ˜Kšœœ˜šœ œ˜Kšœ œ˜šœœœ˜!Kšœœ˜ šœœ ˜Kšœ˜#Kšœœœ˜-—Kšœ˜—Kšœ˜—šœ˜ Kšœœ œ œ˜QKšœ˜Kšœ˜Kšœ˜—Kšœ˜K˜—š Ÿœœ œœœœ˜GKšœ œ˜K˜ Kšœ œ˜Kšœœœ˜#Kšœœœ œ˜ šŸœœ œ œ˜@šœœœ˜&K™Kšœ#˜#Kšœ˜Kšœ˜Kšœ˜—Kšœ˜Kšœ5œ˜=Kšœ˜—š œœœœœœ˜@Kšœœ˜Kšœ œ˜Kšœœ˜Kšœ œ˜šœ‘,˜/Kšœ œ œ˜=Kšœ2˜9šœ ˜Kšœœ˜ ˜ K˜(Kšœ œ œ%˜@Kšœ>˜EKšœœ˜*K˜—Kšœœ˜—Kšœ/˜/Kšœ˜—Kšœ œ˜-Kšœ˜šœ‘,˜/Kšœ œ œ˜=Kšœ2˜9šœ ˜Kšœœ˜ ˜ K˜(Kšœ œ œ%˜@Kšœœ˜ Kš œœœœœœ˜?Kšœ>˜EKšœ?˜?K˜—Kšœœ˜—Kšœ/˜/Kšœ˜—Kšœ˜šœ‘˜Kšœ œ œ˜=Kšœ2˜9šœ ˜Kšœœ˜ Kšœœ˜ ˜ K˜(Kšœ œ œ$˜@Kšœœ˜%Kšœ œ˜(Kšœ"˜"Kšœ˜Kšœ˜Kšœ7˜7Kšœ?˜FKšœ˜Kšœ˜Kšœ˜Kšœ˜KšœS˜SKšœW˜WKšœG˜GKšœQœ!˜vKšœF˜FKšœ6˜6KšœW˜WKšœ"˜"šœ˜K–9[base: ROPE, start: INT _ 0, len: INT _ 2147483647]™°Jšœœ œ!˜GJšœœ œ!˜@Jšœœ@˜HKšœ”˜”Kšœ˜—Kšœ œ˜K˜—˜K˜.Kšœ œ œ*˜EKšœœ˜%Kšœœ˜%Kšœ"˜"Kšœ%˜%Kšœ˜Kšœ6˜6Kšœ>˜Ešœœ˜!Kšœ'œœ œœœ œœœœœœ˜ΚKšœ˜—šœœ˜š œœœœ œ˜JKšœ!˜!Kšœ&˜&Kšœ˜—Kšœ˜—Kšœ˜Kšœ˜Kšœ˜Kšœ˜KšœS˜SKšœW˜WKšœG˜GKšœQœ$˜yKšœ1‘-˜^šœ1˜1K˜-K˜,K˜—Kšœœ8˜PKšœ6˜6KšœW˜WKšœœ#˜Cšœ˜K–9[base: ROPE, start: INT _ 0, len: INT _ 2147483647]™°Jšœœ œ$˜HJšœœ œ!˜@Jšœœ>˜FKšœ”˜”Kšœ˜—Kšœ œ˜K˜—Kšœœ‘˜7—Kšœ/˜/Kšœ˜—Kšœ˜—Kšœ œ˜%Kšœ!˜!Kšœ+˜+Kšœ$‘$Ρcdl‘˜PKšœœ=˜GK˜—K˜šŸœœœœ˜2šœœ˜Kšœœ˜(Kšœ$œœœ'˜šœ˜ Kšœœ6˜>Kšœœ6˜>Kšœœœœ˜$Kš œœ œœœ˜6Kšœœ˜Kšœ˜——Kšœ˜K˜—šŸ œœ œœ œœœ˜LKšœ œœ˜'Kšœœœ œ˜šœœ˜Kšœœœ˜*Kšœ&˜&Kšœœœ˜Kšœœœ œ˜Kš œœœœœœ˜Kšœ˜ Kšœ,˜0Kšœ3˜7—Kšœ˜Kšœ˜—Kšœ˜ Kšœ˜K˜—š Ÿ œœ œœ œ˜FKšœœœœ˜1Kšœœœœ œœœœœ œœœœœ˜^Kšœœœœ œœœœœ œœœœœ˜_š œœœœœ˜CKšœ˜Kšœœ ˜Kšœœ˜)Kšœœ˜)Kšœ˜—šœœ ˜šœœ˜"Kšœœ(˜BKšœ˜Kšœ˜Kš œœœœœœ˜@Kšœ˜Kšœ˜—Kšœ˜—š œœœœœ˜CKšœ˜Kšœ+˜+šœœœ˜)šœ˜ ˜ Kšœ?˜?šœ(œ˜0Kšœœ!˜)Kšœœ!˜)KšœœB˜HKšœ²˜²Kšœ:˜:Kšœ8˜8šœ˜Kšœ œ,˜SKšœ=˜A—Kšœ˜—K˜—˜KšœE˜Ešœ-œ˜5Kšœœ;˜CKšœœ;˜CKšœœB˜HKšœž˜žKšœ8˜8šœ˜Kšœ œ,˜SKšœ=˜A—K˜—K˜—Kšœœ˜—Kšœ˜—Kšœ˜—Kšœœ œ‘3˜MKšœ ˜Kšœ˜K˜—šŸ œœ)œ˜VKšœj˜pKšœ˜K˜—šŸœœœœ'˜dKšœ0˜0Kšœ œ œ$˜=Kšœ&œœ˜3Kšœ9˜9šœœ˜Kšœœe˜q—Kšœ˜Kšœ˜K˜—šŸ œœœœ$˜^Kšœ*˜*Kšœœ œ!˜BKšœœœ˜,Kšœ1˜1šœœ˜Kšœœr˜~—Kšœ˜Kšœ˜K˜—šŸ œœ*œœ˜LKšœ œ ˜Kšœ/˜/Kšœ œœ œœœœœ˜XKšœ&œ˜1K˜—K˜šŸ œœ*œ˜XKšœ œ ˜Jšœœœœœ œœœœœœ$˜ŸKšœ/˜/šœœœœ œœ%œ˜ˆšœœ˜š œ œœœ œœ˜JKšœœœ œ˜GKšœ$˜$Kšœ˜—Kšœ5˜5Kšœ˜%—Kšœ˜—Kšœ˜K˜—K˜šŸ œœ*œœ˜MKšœ œ ˜Kšœ/˜/š œ œœ œœ˜GKšœ=˜=Kšœœœœ˜9Kšœ˜—Kšœ ˜K˜—K˜šŸ œœ*œœ˜MKšœ œ ˜Kšœœ˜:K˜—K˜šŸ œœ*œ ˜cKšœ œ ˜šœœ˜ Kšœœœœ˜GKšœ˜ Kšœ˜—Kšœ˜ K˜—K˜šŸ œœ*œ˜RKšœ œ ˜Kšœ/˜/š œ œœ œœ˜GKšœ=˜=Kšœœœœ,˜LKšœ˜—Kšœœœ'˜KKšœB˜HK˜—K˜šŸœœœ˜>Kšœ œ ˜Kšœ#˜#KšœR˜Ršœœ ˜Kšœ%˜%šœ œœ˜šœœœ˜1Kšœ8˜8šœœœ˜Kšœ"˜"Kš œ œœœœ˜uKšœ˜—Kšœ˜—Kšœ˜—Kšœ˜—Kšœ˜%K˜—K˜Kš Ÿœœ5œœœ ˜dK˜KšŸ œœ5œœ˜ƒK˜KšŸ œœ5œœ˜ƒK˜KšŸœœ5œœ˜‡K˜šŸ œœœœ#˜YKšœ*œ˜.KšœE˜Ešœ-œœ˜=Kšœ œ œ!˜;Kšœ œ6˜EKšœ#˜#Kšœ5˜<šœ)œ˜1Kšœœ2˜CKšœ œ œ!˜:Kšœ"˜"KšœK˜KKšœœ˜7šœ œ œ˜!Kšœ œ œ˜7šœ*˜*KšœT˜TK™>K˜K˜—Kšœ|˜ƒKšœ˜—Kšœ˜—Kšœ˜—Kšœ ˜Kšœ˜K˜—š Ÿœœ œœœ˜,Kšœ œœ˜0Kšœœœ˜3Kšœœ˜"Kšœ˜K˜—Kšœ œ ˜Kšœ œ ˜šŸ œœ9œ˜cK™4Kšœœ˜š œœœ$œœ˜HKšœ˜šœœ˜šœ˜ Kšœ˜šœ˜KšœJ˜Jšœ-œ˜5šœœœ˜KšœN˜NKšœX˜XKšœ5œ˜9Kšœ œ œœœœœœœœ˜‡Kšœ˜—Kšœœœ˜ Kšœ˜—Kšœ˜—Kšœœ˜—Kšœ˜—Kšœ˜—Kš œœœœ‘˜-Kšœ˜Kšœ˜K˜—šŸœœœœ˜WK™JKšœbœ˜lKšœ˜K˜—šŸœœF˜RKšœ œ ˜šœ˜šœ˜Kšœ/˜/Kšœ0˜0Kšœœœ(œ˜<šœ˜ Kšœ0˜0šœ˜Kšœ=˜=šœ œœ˜Kšœ+˜+Kšœ˜Kšœ'˜'Kšœ˜—Kšœ˜—Kšœœ˜—Kšœ˜—šœ˜šœ˜Kšœ&˜*Kšœ4˜8—Kšœ˜——K˜—K˜šŸ œœœ˜LKšœ}˜}Kšœ7œ˜=Kšœ˜K˜—K˜šŸœœœd˜˜š Ÿ œœœœœ˜-K˜'Kšœœ œ!˜?KšœŽ˜ŽKšœ+œœ˜HKšœR˜XKšœ˜—Kšœœ˜%Kšœ œ˜Kšœœ ˜šŸœœœ#œ˜AKšœ'˜'Kšœœ œ$˜Kšœ œ˜,Kš œœœ œœ˜DKšœ œI˜VKš œ œœœœ˜Kšœœœ˜š œ œ œœ œœ˜DKšœ œ$˜3šœœ'œœ˜:Kš œ-œœœœ œ˜iKšœ˜Kšœ œ˜Kšœ˜—Kšœ œ˜#Kšœ˜—Kšœ5˜7šœ œœ˜Kš œ œœœœ˜Kšœœ˜Kšœœ˜1š œœœœœœ˜?Kšœœ œ˜(Kšœœœ˜2Kšœœ˜ Kšœ œ˜ Kšœ#œ8œ˜|Kšœ˜—Kšœ'˜'Kšœ4˜4Kšœ!˜!Kšœ ˜Kšœ˜—Kšœ˜—Kšœœ˜ Kšœ˜K˜—š Ÿœœ&œœœ˜jšœ(œœ˜7Kšœ œ;˜HKš œ œœœœ˜š œ œ œœ œœ˜DKšœ œ˜#Kšœ˜—Kšœ5˜7šœ œœ˜Kš œ œœœœ˜Kšœœ˜Kšœœ˜1š œœœœœœ˜?Kšœœ œ˜(Kšœœœ˜2Kšœœ˜ Kšœ œ˜ Kšœ#œ8œ˜|Kšœ˜—Kšœ'˜'Kšœ4˜4Kšœ!˜!Kšœ ˜Kšœ˜—Kšœ˜—Kšœœ˜ Kšœ˜K˜—š Ÿœœ&œœœ˜ošœ(œœ˜7Kšœ œ;˜HKš œ œœœœ˜š œ œ œœ œœ˜DKšœ œ˜#Kšœ˜—Kšœ5˜7šœ œœ˜Kš œ œœœœ˜Kšœœ˜Kšœœ˜1š œœœœœœ˜?Kšœœ œ˜(Kšœœœ˜2Kšœœ˜ Kšœ œ˜ Kšœ#œ8œ˜|Kšœ˜—Kšœ'˜'Kšœ4˜4Kšœ!˜!Kšœ ˜Kšœ˜—Kšœ˜—Kšœœ˜ Kšœ˜K˜—šœ$œ4œ˜aK˜—šœ+œ<œ˜pK˜—Kšœ0œAœ˜zK˜Kšœ*œ™0šœ1œ˜7K˜—KšœX™XKšœX™XšœX™XK™——Kšœ˜J˜J˜—…—zΠ¦β