DIRECTORY Basics USING [bitsPerWord, LongMult], ImagerColor USING [Color, ConstantColor, SampledColor, SpecialColor], ImagerRaster USING [Device, DeviceRep, RectangleProc, RunProc], ImagerPixelMap USING [DeviceRectangle, Function, PixelMap], ImagerTransformation USING [PreRotate, Transformation, Translate], PrincOps USING [BBTableSpace, BitBltTable, BitBltTablePtr, zBNDCK, zINC], PrincOpsUtils USING [AlignedBBTable, BITBLT]; ImagerRasterLFImpl: CEDAR PROGRAM IMPORTS Basics, ImagerTransformation, PrincOpsUtils ~ BEGIN Device: TYPE ~ ImagerRaster.Device; RunProc: TYPE ~ ImagerRaster.RunProc; RectangleProc: TYPE ~ ImagerRaster.RectangleProc; DeviceRectangle: TYPE ~ ImagerPixelMap.DeviceRectangle; Transformation: TYPE ~ ImagerTransformation.Transformation; Color: TYPE ~ ImagerColor.Color; ConstantColor: TYPE ~ ImagerColor.ConstantColor; SampledColor: TYPE ~ ImagerColor.SampledColor; SpecialColor: TYPE ~ ImagerColor.SpecialColor; GrayArray: TYPE ~ ARRAY [0..16) OF CARDINAL; grayHeight: NAT ~ 16; -- must not exceed 16, should be a power of 2 checkeredGray: GrayArray ~ [ 05555H, -- . @ . @ . @ . @ . @ . @ . @ . @ 0AAAAH, -- @ . @ . @ . @ . @ . @ . @ . @ . 05555H, -- . @ . @ . @ . @ . @ . @ . @ . @ 0AAAAH, -- @ . @ . @ . @ . @ . @ . @ . @ . 05555H, -- . @ . @ . @ . @ . @ . @ . @ . @ 0AAAAH, -- @ . @ . @ . @ . @ . @ . @ . @ . 05555H, -- . @ . @ . @ . @ . @ . @ . @ . @ 0AAAAH, -- @ . @ . @ . @ . @ . @ . @ . @ . 05555H, -- . @ . @ . @ . @ . @ . @ . @ . @ 0AAAAH, -- @ . @ . @ . @ . @ . @ . @ . @ . 05555H, -- . @ . @ . @ . @ . @ . @ . @ . @ 0AAAAH, -- @ . @ . @ . @ . @ . @ . @ . @ . 05555H, -- . @ . @ . @ . @ . @ . @ . @ . @ 0AAAAH, -- @ . @ . @ . @ . @ . @ . @ . @ . 05555H, -- . @ . @ . @ . @ . @ . @ . @ . @ 0AAAAH -- @ . @ . @ . @ . @ . @ . @ . @ . ]; desktopGray: GrayArray ~ [ 01111H, -- . . . @ . . . @ . . . @ . . . @ 01111H, -- . . . @ . . . @ . . . @ . . . @ 04444H, -- . @ . . . @ . . . @ . . . @ . . 04444H, -- . @ . . . @ . . . @ . . . @ . . 01111H, -- . . . @ . . . @ . . . @ . . . @ 01111H, -- . . . @ . . . @ . . . @ . . . @ 04444H, -- . @ . . . @ . . . @ . . . @ . . 04444H, -- . @ . . . @ . . . @ . . . @ . . 01111H, -- . . . @ . . . @ . . . @ . . . @ 01111H, -- . . . @ . . . @ . . . @ . . . @ 04444H, -- . @ . . . @ . . . @ . . . @ . . 04444H, -- . @ . . . @ . . . @ . . . @ . . 01111H, -- . . . @ . . . @ . . . @ . . . @ 01111H, -- . . . @ . . . @ . . . @ . . . @ 04444H, -- . @ . . . @ . . . @ . . . @ . . 04444H -- . @ . . . @ . . . @ . . . @ . . ]; Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD[ frame: ImagerPixelMap.PixelMap, -- the bitmap function: ImagerPixelMap.Function, -- for Constant or Gray gray: GrayArray -- for Constant or Gray ]; defaultPixelsPerInch: REAL ~ 72; metersPerInch: REAL ~ 0.0254; Create: PROC[frame: ImagerPixelMap.PixelMap, pixelsPerInch: REAL _ defaultPixelsPerInch] RETURNS[Device] ~ { data: Data ~ NEW[DataRep _ [frame: frame, function: [null, null], gray: ALL[177777B]]]; pixelsPerMeter: REAL ~ pixelsPerInch/metersPerInch; surfaceToDevice: Transformation ~ ImagerTransformation.Translate[[frame.sSize, 0]]; surfaceToDevice.PreRotate[90]; RETURN[NEW[ImagerRaster.DeviceRep _ [type: $LFDisplay, setColor: SetColor, setPriorityImportant: SetPriorityImportant, maskRuns: MaskRunsConst, maskRectangles: MaskRectanglesConst, surfaceToDevice: surfaceToDevice, clipBox: [sMin: 0, fMin: 0, sSize: frame.sSize, fSize: frame.fSize], metersToSurface: [pixelsPerMeter, pixelsPerMeter], viewUnitsPerPixel: 1, data: data]]]; }; SetColor: PROC[device: Device, color: Color, viewToDevice: Transformation] ~ { data: Data ~ NARROW[device.data]; const: BOOL _ FALSE; WITH color SELECT FROM color: ConstantColor => { data.function _ [null, null]; SELECT color.f FROM 1 => { data.gray _ ALL[177777B]; const _ TRUE }; 0 => { data.gray _ ALL[0]; const _ TRUE }; ENDCASE => data.gray _ checkeredGray; IF const THEN { device.maskRuns _ MaskRunsConst; device.maskRectangles _ MaskRectanglesConst; } ELSE { device.maskRuns _ MaskRunsGray; device.maskRectangles _ MaskRectanglesGray; }; }; color: SampledColor => { ERROR; -- not yet implemented }; color: SpecialColor => SELECT color.ref FROM $XOR => { data.function _ [xor, null]; data.gray _ ALL[177777B]; device.maskRuns _ MaskRunsConst; device.maskRectangles _ MaskRectanglesConst; }; ENDCASE => ERROR; -- unknown special color ENDCASE => ERROR; -- unknown color variant }; SetPriorityImportant: PROC[device: Device, priorityImportant: BOOL] ~ { }; nullBitBltTable: PrincOps.BitBltTable ~ [ dst: [word: NIL, bit: 0], dstBpl: 0, src: [word: NIL, bit: 0], srcDesc: [srcBpl[0]], width: 0, height: 0, flags: []]; Check: PROC[x: INTEGER, max: NAT] RETURNS[NAT] ~ TRUSTED MACHINE CODE { PrincOps.zINC; PrincOps.zBNDCK }; MaskRectanglesGray: PROC[device: Device, rectangles: PROC[RectangleProc]] ~ TRUSTED { data: Data ~ NARROW[device.data]; bbspace: PrincOps.BBTableSpace; bb: PrincOps.BitBltTablePtr ~ PrincOpsUtils.AlignedBBTable[@bbspace]; gray: LONG POINTER ~ @data.gray; base: LONG POINTER ~ data.frame.refRep.pointer; rast: CARDINAL ~ data.frame.refRep.rast; fSizeDevice: NAT ~ data.frame.fSize; sSizeDevice: NAT ~ data.frame.sSize; rectangle: PROC[r: DeviceRectangle] ~ TRUSTED { f: NAT ~ Check[r.fMin, fSizeDevice]; s: NAT ~ Check[r.sMin, sSizeDevice]; bb.dst.word _ base+Basics.LongMult[s, rast]+f/Basics.bitsPerWord; bb.dst.bit _ bb.src.bit _ f MOD Basics.bitsPerWord; bb.src.word _ gray+(bb.srcDesc.gray.yOffset _ s MOD grayHeight); bb.width _ Check[r.fSize, fSizeDevice-f]; bb.height _ Check[r.sSize, sSizeDevice-s]; PrincOpsUtils.BITBLT[bb]; }; bb^ _ nullBitBltTable; bb.dstBpl _ rast*Basics.bitsPerWord; bb.srcDesc.gray _ [yOffset: 0, widthMinusOne: 0, heightMinusOne: grayHeight-1]; bb.flags _ [direction: forward, disjoint: TRUE, gray: TRUE, srcFunc: data.function.srcFunc, dstFunc: data.function.dstFunc]; rectangles[rectangle]; }; MaskRectanglesConst: PROC[device: Device, rectangles: PROC[RectangleProc]] ~ TRUSTED { data: Data ~ NARROW[device.data]; bbspace: PrincOps.BBTableSpace; bb: PrincOps.BitBltTablePtr ~ PrincOpsUtils.AlignedBBTable[@bbspace]; gray: LONG POINTER ~ @data.gray; base: LONG POINTER ~ data.frame.refRep.pointer; rast: CARDINAL ~ data.frame.refRep.rast; fSizeDevice: NAT ~ data.frame.fSize; sSizeDevice: NAT ~ data.frame.sSize; rectangle: PROC[r: DeviceRectangle] ~ TRUSTED { f: NAT ~ Check[r.fMin, fSizeDevice]; s: NAT ~ Check[r.sMin, sSizeDevice]; bb.dst.word _ base+Basics.LongMult[s, rast]+f/Basics.bitsPerWord; bb.dst.bit _ f MOD Basics.bitsPerWord; bb.width _ Check[r.fSize, fSizeDevice-f]; bb.height _ Check[r.sSize, sSizeDevice-s]; PrincOpsUtils.BITBLT[bb]; }; bb^ _ nullBitBltTable; bb.dstBpl _ rast*Basics.bitsPerWord; bb.src.word _ gray; bb.src.bit _ 0; bb.srcDesc.gray _ [yOffset: 0, widthMinusOne: 0, heightMinusOne: grayHeight-1]; bb.flags _ [direction: forward, disjoint: TRUE, gray: TRUE, srcFunc: data.function.srcFunc, dstFunc: data.function.dstFunc]; rectangles[rectangle]; }; MaskRunsGray: PROC[device: Device, runs: PROC[RunProc]] ~ TRUSTED { data: Data ~ NARROW[device.data]; bbspace: PrincOps.BBTableSpace; bb: PrincOps.BitBltTablePtr ~ PrincOpsUtils.AlignedBBTable[@bbspace]; gray: LONG POINTER ~ @data.gray; base: LONG POINTER ~ data.frame.refRep.pointer; rast: CARDINAL ~ data.frame.refRep.rast; fSizeDevice: NAT ~ data.frame.fSize; sSizeDevice: NAT ~ data.frame.sSize; run: PROC[sMin, fMin: INTEGER, fSize: NAT] ~ TRUSTED { f: NAT ~ Check[fMin, fSizeDevice]; s: NAT ~ Check[sMin, sSizeDevice]; bb.dst.word _ base+Basics.LongMult[s, rast]+f/Basics.bitsPerWord; bb.dst.bit _ bb.src.bit _ f MOD Basics.bitsPerWord; bb.src.word _ gray+(bb.srcDesc.gray.yOffset _ s MOD grayHeight); bb.width _ Check[fSize, fSizeDevice-f]; bb.height _ Check[1, sSizeDevice-s]; PrincOpsUtils.BITBLT[bb]; }; bb^ _ nullBitBltTable; bb.dstBpl _ rast*Basics.bitsPerWord; bb.srcDesc.gray _ [yOffset: 0, widthMinusOne: 0, heightMinusOne: grayHeight-1]; bb.flags _ [direction: forward, disjoint: TRUE, gray: TRUE, srcFunc: data.function.srcFunc, dstFunc: data.function.dstFunc]; runs[run]; }; MaskRunsConst: PROC[device: Device, runs: PROC[RunProc]] ~ TRUSTED { data: Data ~ NARROW[device.data]; bbspace: PrincOps.BBTableSpace; bb: PrincOps.BitBltTablePtr ~ PrincOpsUtils.AlignedBBTable[@bbspace]; gray: LONG POINTER ~ @data.gray; base: LONG POINTER ~ data.frame.refRep.pointer; rast: CARDINAL ~ data.frame.refRep.rast; fSizeDevice: NAT ~ data.frame.fSize; sSizeDevice: NAT ~ data.frame.sSize; run: PROC[sMin, fMin: INTEGER, fSize: NAT] ~ TRUSTED { f: NAT ~ Check[fMin, fSizeDevice]; s: NAT ~ Check[sMin, sSizeDevice]; bb.dst.word _ base+Basics.LongMult[s, rast]+f/Basics.bitsPerWord; bb.dst.bit _ f MOD Basics.bitsPerWord; bb.width _ Check[fSize, fSizeDevice-f]; bb.height _ Check[1, sSizeDevice-s]; PrincOpsUtils.BITBLT[bb]; }; bb^ _ nullBitBltTable; bb.dstBpl _ rast*Basics.bitsPerWord; bb.src.word _ gray; bb.src.bit _ 0; bb.srcDesc.gray _ [yOffset: 0, widthMinusOne: 0, heightMinusOne: grayHeight-1]; bb.flags _ [direction: forward, disjoint: TRUE, gray: TRUE, srcFunc: data.function.srcFunc, dstFunc: data.function.dstFunc]; runs[run]; }; END. jImagerRasterLFImpl.mesa Copyright c 1984 Xerox Corporation. All rights reserved. Michael Plass, June 19, 1984 1:19:53 pm PDT Doug Wyatt, October 16, 1984 6:05:35 pm PDT IF x IN[0..max] THEN RETURN[x] ELSE ERROR RuntimeError.BoundsFault Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD[ value: CARDINAL, -- for ApplyMaskConstant tile: ImagerPixelMap.Tile, -- for ApplyMaskTile tileSamples: ImagerColor.SampledColor, -- for ApplyMaskTile function: ImagerMask.Function, -- for ApplyMaskConstant or ApplyMaskTile invertOutput, transparent: BOOL, -- for ApplyMaskHalftone source: PixelArray, -- for ApplyMaskHalftone transformation: Transformation, -- for ApplyMaskHalftone deviceBrick: ImagerHalftone.DeviceBrick _ NIL, -- for ApplyMaskHalftone deviceBrickMaxSampleValue: INT _ -1, frame: ImagerPixelMap.PixelMap -- the bitmap ]; SetColor: PROC[device: Display, color: Color, vx, vy: INTEGER] ~ { data: Data ~ NARROW[device.data]; WITH color SELECT FROM color: ConstantColor => { tile: ImagerPixelMap.Tile ~ data.tile; Runs: PROC[run: PROC[sMin, fMin: INTEGER, fSize: NAT]] ~ { FOR s: INTEGER IN [tile.sOrigin..tile.sOrigin + tile.sSize) DO run[s, tile.fOrigin, tile.fSize]; ENDLOOP; }; SetTileSamples[data.tileSamples, Real.RoundC[IntensityFromColor[color]*255]]; ImagerHalftone.Halftone[ dest: [sOrigin: tile.sOrigin, fOrigin: tile.fOrigin, sMin: 0, fMin: 0, sSize: tile.sSize, fSize: tile.fSize, refRep: tile.refRep], runs: Runs, source: data.tileSamples.pa, transformation: ImagerTransformation.Rotate[0], deviceBrick: lfBrick ]; tile.sOrigin _ tile.sOrigin + SurfaceOriginS[data]-vy; tile.fOrigin _ tile.fOrigin + vx; data.function _ [null, null]; device.ApplyMask _ ApplyMaskTile; }; color: SampledColor => { pa: PixelArray ~ color.pa; data.source _ pa; data.transformation^ _ device.surfaceToDevice^; data.transformation.PreTranslate[vx, vy]; -- view to device data.transformation.PreMultiply[color.m]; -- color to device data.transformation.PreMultiply[pa.m]; -- pa to device IF data.deviceBrickMaxSampleValue#pa.maxSampleValue THEN { data.deviceBrickMaxSampleValue _ pa.maxSampleValue; data.deviceBrick _ ImagerHalftone.MakeSquareBrick[4, 3, 2, 1, 1, pa.maxSampleValue]; }; SELECT color.colorOperator FROM $SampledBlack => data.invertOutput _ TRUE; $Intensity => data.invertOutput _ FALSE; ENDCASE => Imager.Error[$UnknownColorModel]; data.transparent _ color.transparent; device.ApplyMask _ ApplyMaskHalftone; }; color: SpecialColor => { WITH color.ref SELECT FROM stipple: REF CARDINAL => { data.tile _ ImagerPixelMaps.TileFromStipple[stipple: stipple^, scratch: data.tile.refRep]; data.function _ [null, null]; device.ApplyMask _ ApplyMaskTile; }; ENDCASE => ERROR Imager.Error[$UnknownSpecialColor]; }; ENDCASE => Imager.Error[$Bug]; }; lfBrick: ImagerHalftone.DeviceBrick _ ImagerHalftone.MakeSquareBrick[4, 3, 2, 1, 1, 255]; IntensityFromColor: PROC[c: ConstantColor] RETURNS[REAL] ~ { WITH c SELECT FROM c: REF ImagerColor.ColorRep[constant][gray] => RETURN[c.f]; c: REF ImagerColor.ColorRep[constant][cie] => RETURN[c.Y]; ENDCASE => ERROR; }; SetTileSamples: PROC [tileSamples: SampledColor, intensity: [0..255]] ~ { WITH tileSamples.pa.data SELECT FROM n: REF NAT => n^ _ intensity; ENDCASE => ERROR; }; ApplyMaskConstant: PROC[device: Display, mask, clipper: Mask, sTranslate, fTranslate: INTEGER] ~ { data: Data ~ NARROW[device.data]; ImagerMask.ApplyConstant[mask: mask, clipper: clipper, dest: data.frame, value: data.value, function: data.function, sTranslate: sTranslate, fTranslate: fTranslate]; }; ApplyMaskTile: PROC[device: Display, mask, clipper: Mask, sTranslate, fTranslate: INTEGER] ~ { data: Data ~ NARROW[device.data]; USING [frame, tile, function] ImagerMask.ApplyTile[mask: mask, clipper: clipper, dest: data.frame, tile: data.tile, function: data.function, sTranslate: sTranslate, fTranslate: fTranslate]; }; ApplyMaskHalftone: PROC[device: Display, mask, clipper: Mask, sTranslate, fTranslate: INTEGER] ~ { data: Data ~ NARROW[device.data]; USING [frame, source, transformation, deviceBrick, invertOutput, transparent] Runs: PROC[run: PROC[sMin, fMin: INTEGER, fSize: NAT]] ~ { ImagerMask.GenerateRuns[mask, clipper, run, sTranslate, fTranslate]; }; ImagerHalftone.Halftone[dest: data.frame, runs: Runs, source: data.source, transformation: data.transformation, deviceBrick: data.deviceBrick, invertOutput: data.invertOutput, transparent: data.transparent]; }; ImagerPrivate.RegisterDevice[LFDisplayClass]; Ê ö˜šœ™Jšœ Ïmœ.™9J™+J™+—J˜šÏk ˜ Jšœžœ˜%Jšœ žœ4˜EJšœ žœ-˜?Jšœžœ'˜;Jšœžœ(˜BJšœ žœ;˜IJšœžœžœ˜-J˜—Jšœžœž˜!Jšžœ,˜3Jšœž˜J˜Jšœžœ˜#Jšœ žœ˜%Jšœžœ˜1Jšœžœ"˜7J˜Jšœžœ'˜;Jšœžœ˜ Jšœžœ˜0Jšœžœ˜.Jšœžœ˜.J˜Jš œ žœžœ žœžœ˜,Jšœ žœÏc-˜CJ˜˜JšÏf*˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*J˜J˜—˜Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*Jš *˜*J˜J˜—Jšœžœžœ ˜šœ žœžœ˜Jšœ Ÿ ˜-Jšœ#Ÿ˜:JšœŸ˜'J˜J˜—Jšœžœ˜ Jšœžœ ˜J˜šÏnœžœ0žœ˜XJšžœ ˜Jšœ žœ8žœ ˜WJšœžœ˜3JšœS˜SJšœ˜šžœžœ,˜6Jšœ?˜?Jšœ=˜=Jšœ!˜!JšœD˜DJšœ2˜2Jšœ˜Jšœ˜—J˜J˜—š¡œžœ@˜NJšœ žœ˜!Jšœžœžœ˜šžœžœž˜˜Jšœ˜šžœ ž˜Jšœžœžœ˜0Jšœžœ žœ˜*Jšžœ˜%—šžœžœ˜Jšœ ˜ Jšœ,˜,J˜—šžœ˜Jšœ˜Jšœ+˜+J˜—J˜—˜JšžœŸ˜J˜—šœžœ ž˜,šœ ˜ Jšœ˜Jšœ žœ ˜Jšœ ˜ Jšœ,˜,J˜—JšžœžœŸ˜*—JšžœžœŸ˜*—Jšœ˜J˜—š¡œžœ$žœ˜GJšœ˜J˜—šœ)˜)Jšœ žœ˜$Jšœ žœ ˜/Jšœ ˜ J˜—š ¡œžœžœžœžœžœ˜0Jšžœžœžœ$˜8Jš žœžœ žœžœžœžœ™BJ˜—š¡œžœžœžœ˜UJšœ žœ˜!J˜JšœE˜EJšœžœžœ˜ Jšœžœžœ˜/Jšœžœ˜(Jšœ žœ˜$Jšœ žœ˜$šœ žœžœ˜/Jšœžœ˜$Jšœžœ˜$JšœA˜AJšœžœ˜3Jšœ0žœ ˜@Jšœ)˜)Jšœ*˜*Jšœžœ˜J˜—Jšœ˜Jšœ$˜$JšœO˜Ošœ*žœžœ˜;Jšœ@˜@—J˜J˜J˜—š¡œžœžœžœ˜VJšœ žœ˜!J˜JšœE˜EJšœžœžœ˜ Jšœžœžœ˜/Jšœžœ˜(Jšœ žœ˜$Jšœ žœ˜$šœ žœžœ˜/Jšœžœ˜$Jšœžœ˜$JšœA˜AJšœžœ˜&Jšœ)˜)Jšœ*˜*Jšœžœ˜J˜—Jšœ˜Jšœ$˜$Jšœ˜Jšœ˜JšœO˜Ošœ*žœžœ˜;Jšœ@˜@—J˜J˜J˜—š¡ œžœžœ žœ˜CJšœ žœ˜!J˜JšœE˜EJšœžœžœ˜ Jšœžœžœ˜/Jšœžœ˜(Jšœ žœ˜$Jšœ žœ˜$š œžœ žœ žœžœ˜6Jšœžœ˜"Jšœžœ˜"JšœA˜AJšœžœ˜3Jšœ0žœ ˜@Jšœ'˜'Jšœ$˜$Jšœžœ˜J˜—Jšœ˜Jšœ$˜$JšœO˜Ošœ*žœžœ˜;Jšœ@˜@—J˜ J˜J˜—š¡ œžœžœ žœ˜DJšœ žœ˜!J˜JšœE˜EJšœžœžœ˜ Jšœžœžœ˜/Jšœžœ˜(Jšœ žœ˜$Jšœ žœ˜$š œžœ žœ žœžœ˜6Jšœžœ˜"Jšœžœ˜"JšœA˜AJšœžœ˜&Jšœ'˜'Jšœ$˜$Jšœžœ˜J˜—Jšœ˜Jšœ$˜$Jšœ˜Jšœ˜JšœO˜Ošœ*žœžœ˜;Jšœ@˜@—J˜ J˜J˜—J™J™Jšœžœžœ ™šœ žœžœ™JšœžœŸ™)JšœŸ™/Jšœ*Ÿ™;JšœŸ)™HJšœžœŸ™9JšœŸ™,Jšœ Ÿ™8Jšœ*žœŸ™GJšœžœ™$JšœŸ ™,J™J™—š¡œžœ(žœ™BJšœ žœ™!šžœžœž™šœ™Jšœ&™&š ¡œžœžœ žœ žœ™:šžœžœžœ+ž™>Jšœ!™!Jšž™—Jšœ™—JšœM™Mšœ™šœ4™4JšœM™M—Jšœ ™ Jšœ™Jšœ/™/Jšœ™Jšœ™—Jšœ6™6Jšœ!™!Jšœ™Jšœ!™!Jšœ™—šœ™Jšœ™J™J™/Jšœ*Ÿ™;Jšœ*Ÿ™