-- PDInterpReaderImpl.mesa
-- Michael Plass, December 2, 1983 5:11 pm
-- Last Edited by: Pier, November 22, 1983 2:45 pm
-- Last Edited by: Lamming, December 9, 1983 1:27 pm
-- Tim Diebert,  5-Sep-86 13:36:47

DIRECTORY
   Environment, Inline, PDInterpBasic, PDFileFormat, PDInterpBitmap,
     PDInterpReader, PDInterpSysCalls, Heap, Stream;
     
PDInterpReaderImpl: PROGRAM
   IMPORTS Inline, PDInterpBitmap, PDInterpSysCalls, Heap, Stream
   EXPORTS PDInterpReader
   = BEGIN OPEN PDInterpReader;

   bitsPerWord: NAT = Environment.bitsPerWord;
   zone: UNCOUNTED ZONE = Heap.systemZone;
      
CheckHerald: PROC [herald: PDFileFormat.Herald] = {
   status: PDInterpBasic.Status ← nil;
   IF herald.maxLoadWord > 200000 THEN status ← unreasonableLoadSize;
   IF NOT (herald.copies IN [1..1000]) THEN
      status ← unreasonableNumberOfCopies;
   IF NOT (herald.imageSSize IN [80..32767])
   OR NOT (herald.imageFSize IN [80..32767]) THEN status ← unreasonableImageSize;
   IF NOT (herald.bandSSize IN [8..32767]) THEN status ← unreasonableBandSSize;
   IF NOT (herald.sResolution IN [10..10000])
   OR NOT (herald.fResolution IN [10..10000]) THEN status ← unreasonableResolution;
   IF herald.version # PDFileFormat.versionValue THEN status ← wrongFormatVersion;
   IF herald.password # PDFileFormat.passwordValue THEN status ← invalidPassword;
   IF status # nil THEN Error[NIL, status, 0, SIZE[PDFileFormat.Herald]];
   };
   
AllocateSpace: PROC [handle: Handle] = {
   private: Private ← handle.private;
   bufferWords: INT ← (Inline.LongMult[handle.herald.bandSSize, (handle.herald.imageFSize+bitsPerWord-1)/bitsPerWord]+100)*2;
   private.loadPointer ← PDInterpSysCalls.AllocateSpace[handle.herald.maxLoadWord+bufferWords];
   private.loadWordsAllocated ← handle.herald.maxLoadWord;
   private.bufferOriginPointer ← private.loadPointer + private.loadWordsAllocated;
   private.bufferWordsAllocated ← bufferWords;
   private.bufferWordCount ← 0;
   };
   
Open: PUBLIC PROC [stream: Stream.Handle] RETURNS [handle: Handle] = {
   bytesTransferred: CARDINAL;
   why: Stream.CompletionCode;
   sst: Stream.SubSequenceType;
   herald: PDFileFormat.Herald;
   heraldPtr: LONG POINTER = @herald;
   [bytesTransferred, why, sst] ← Stream.GetBlock[stream, [blockPointer: heraldPtr, startIndex: 0, stopIndexPlusOne: Environment.bytesPerWord*SIZE[PDFileFormat.Herald]]];
   IF bytesTransferred < Environment.bytesPerWord*SIZE[PDFileFormat.Herald] THEN Error[NIL, unexpectedEOF, 0, SIZE[PDFileFormat.Herald]];
   CheckHerald[herald];
   BEGIN
      private: Private ← zone.NEW[PrivateRep];
      handle ← zone.NEW[Rep ← [
         stream: stream,
         herald: herald,
         image: [0, FALSE, FALSE, FALSE, black, 0, 0, 0, 0],
         bandNumber: 0,
         sMinBand: 0,
         sSizeBand: 0,
         colorType: none,
         colorTileLoadAddress: -1,
         priority: 0,
         loadWords: 0,
         private: private,
         index: 0,
         page: 0,
         pass: 0,
         status: nil,
         warningCount: 0
         ]];
      handle.index ← SIZE[PDFileFormat.Herald];
      handle.sSizeBand ← herald.bandSSize;
      {ENABLE
         UNWIND => {
            zone.FREE[@private];
            zone.FREE[@handle];
            };
         AllocateSpace[handle];
         };
      END;
   PDInterpSysCalls.SetDisplayLights[107];
   };
   
Close: PUBLIC PROC [handle: Handle] RETURNS [stream: Stream.Handle] = {
   private: Private ← handle.private;
   stream ← handle.stream;
   IF private.loadPointer # NIL THEN PDInterpSysCalls.FreeSpace[private.loadPointer];
   zone.FREE[@private];
   zone.FREE[@handle];
   PDInterpSysCalls.SetDisplayLights[109];
   };
   
ColorTileFromLoad: PUBLIC PROC [handle: Handle, colorTileLoadAddress: INT, scratchPointer: LONG POINTER, scratchWords: INT] RETURNS [PDInterpBitmap.Tile] = {
   private: Private = handle.private;
   tile: LONG POINTER TO PDFileFormat.Tile = private.loadPointer + colorTileLoadAddress;
   words: INT = Inline.LongMult[tile.sSize, (tile.fSize+(bitsPerWord-1))/bitsPerWord];
   bitmap: PDInterpBitmap.BitmapDesc← PDInterpBitmap.Reshape[
      tile + SIZE[PDFileFormat.Tile],
      words,
      [tile.sMin, tile.fMin, tile.sSize, tile.fSize]
      ];
   RETURN [PDInterpBitmap.CreateTile[
      rectangle: [tile.sMin, tile.fMin, tile.sSize, tile.fSize],
      phase: tile.phase,
      rasterPointer: tile + SIZE[PDFileFormat.Tile],
      scratchPointer: scratchPointer,
      scratchWords: scratchWords
      ]];
   };
   
LongBlockTransfer: PROC [source: LONG POINTER, count: INT, dest: LONG POINTER] = {
   WHILE count > LAST[CARDINAL] DO
      Inline.LongCOPY[source, LAST[CARDINAL], dest];
      source ← source + LAST[CARDINAL];
      dest ← dest + LAST[CARDINAL];
      count ← count - LAST[CARDINAL];
      ENDLOOP;
   Inline.LongCOPY[source, count, dest];
   };
   
Private: TYPE = LONG POINTER TO PrivateRep;
PrivateRep: PUBLIC TYPE = RECORD [
   loadPointer: LONG POINTER ← NIL,
   loadWordsAllocated: INT ← 0,
   bufferOriginPointer: LONG POINTER ← NIL,
   bufferWordsAllocated: INT ← 0,
   bufferWordCount: INT ← 0
   ];
   
BlockDescription: TYPE = RECORD [pointer: LONG POINTER, words: CARDINAL];
ReadLocate: PROC [handle: Handle, words: INT]
   RETURNS [blockDescription: BlockDescription] = {
   -- The buffer storage may get re-used with the next ReadLocate or ReadBlock;
   private: Private ← handle.private;
   bytesWanted: CARDINAL ← Environment.bytesPerWord*MIN[words, private.bufferWordsAllocated];
   bytesTransferred: CARDINAL;
   why: Stream.CompletionCode;
   sst: Stream.SubSequenceType;
   [bytesTransferred, why, sst] ← Stream.GetBlock[handle.stream, [blockPointer: private.bufferOriginPointer, startIndex: 0, stopIndexPlusOne: bytesWanted]];
   IF bytesTransferred < bytesWanted THEN {handle.status ← unexpectedEOF; Error[handle, unexpectedEOF, handle.index, words]};
   blockDescription.pointer ← private.bufferOriginPointer;
   blockDescription.words ← private.bufferWordCount ← bytesTransferred/Environment.bytesPerWord;
   handle.index ← handle.index + blockDescription.words;
   };
   
ReadBlock: PROC [handle: Handle, dest: LONG POINTER, words: INT] = {
   bytesWanted: CARDINAL ← Environment.bytesPerWord*words;
   bytesTransferred: CARDINAL;
   why: Stream.CompletionCode;
   sst: Stream.SubSequenceType;
   [bytesTransferred, why, sst] ← Stream.GetBlock[handle.stream, [blockPointer: dest, startIndex: 0, stopIndexPlusOne: bytesWanted]];
   IF bytesTransferred < bytesWanted THEN {handle.status ← unexpectedEOF; Error[handle, unexpectedEOF, handle.index, words]};
   handle.index ← handle.index + bytesTransferred/Environment.bytesPerWord;
   };
   
CheckBB: PROC [handle: Handle, sMin, fMin, sSize, fSize: CARDINAL, errorWordCount: INT] = {
   IF LONG[sMin] + sSize > handle.herald.imageSSize
   OR LONG[fMin] + fSize > handle.herald.imageFSize
   OR fMin < handle.image.fMinPage
   OR fMin + fSize > handle.image.fMinPage + handle.image.fSizePage
   THEN RaisePDError[handle, objectOutOfBounds, errorWordCount];
   IF sMin + sSize <= handle.sMinBand
   OR sMin >= handle.sMinBand+handle.sSizeBand THEN {
      RaisePDWarning[handle, objectOutOfBand, errorWordCount];
      };
   };
   
Get: PUBLIC PROC [handle: Handle] RETURNS [CommandBuffer] = {
   private: Private ← handle.private;
   command: PDFileFormat.Command;
   BadLoadReference: PROC [dataWords: INT] = {
      RaisePDError[handle, badLoadReference, SIZE[PDFileFormat.Command]+dataWords];
      };
   IF handle.priority = LAST[INT] THEN {
      ans: CommandBuffer.stateChange;
      ans.loadChangeStart ← ans.loadChangeLength ← 0;
      handle.sMinBand ← handle.sMinBand + handle.sSizeBand;
      handle.bandNumber ← handle.bandNumber + 1;
      handle.priority ← 0;
      handle.colorType ← ink;
      ans.whatChanged ← bandChange;
      IF handle.bandNumber = handle.image.nBands THEN {
         ans.whatChanged ← imageEnd;
         handle.colorType ← none;
         handle.status ← betweenPages;
         };
      RETURN [ans];
      };
   ReadBlock[handle, @command, SIZE[PDFileFormat.Command]];
   WITH command SELECT FROM
      imagingCommand: PDFileFormat.Command.imaging => {
         IF handle.colorType = none THEN {
            RaisePDError[handle, missingStartImage, SIZE[PDFileFormat.Command]];
            ERROR
            };
         SELECT imagingCommand.com FROM
            maskSamplesRef => {
               ans: CommandBuffer.maskSamples;
               maskSamplesRef: PDFileFormat.MaskSamplesRef;
               loadAddress: Environment.LongNumber;
               samples: LONG POINTER TO PDFileFormat.SampleArray;
               words: INT;
               ReadBlock[handle, @maskSamplesRef, SIZE[PDFileFormat.MaskSamplesRef]];
               loadAddress.highbits ← imagingCommand.addrHighBits;
               loadAddress.lowbits ← maskSamplesRef.addrLowBits;
               ans.loadAddress ← loadAddress.li;
               IF ans.loadAddress < 0 OR ans.loadAddress + SIZE[PDFileFormat.SampleArray] > handle.loadWords THEN BadLoadReference[SIZE[PDFileFormat.MaskSamplesRef]];
               samples ← private.loadPointer + loadAddress.li;
               words ← Inline.LongMult[samples.sSize, (samples.fSize+(bitsPerWord-1))/bitsPerWord];
               IF ans.loadAddress + SIZE[PDFileFormat.SampleArray] + words > handle.loadWords THEN BadLoadReference[SIZE[PDFileFormat.MaskSamplesRef]];
               CheckBB[handle, maskSamplesRef.sMin, maskSamplesRef.fMin, samples.sSize, samples.fSize, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.MaskSamplesRef]];
               ans.samples ← [sOrigin: maskSamplesRef.sMin, fOrigin: maskSamplesRef.fMin, sMin: 0, fMin: 0, sSize: samples.sSize, fSize: samples.fSize, pointer: samples+SIZE[PDFileFormat.SampleArray], rast: (samples.fSize+(bitsPerWord-1))/bitsPerWord, lines: samples.sSize];
               RETURN [ans]
               };
            maskRunGroupRef => {
               ans: CommandBuffer.maskRunGroup;
               maskRunGroupRef: PDFileFormat.MaskRunGroupRef;
               loadAddress: Environment.LongNumber;
               runGroup: LONG POINTER TO PDFileFormat.RunGroup;
               run: LONG POINTER TO PDFileFormat.Run;
               s: CARDINAL ← 0;
               runCount: CARDINAL ← 0;
               words: INT ← 0;
               fSize: CARDINAL ← 0;
               ReadBlock[handle, @maskRunGroupRef, SIZE[PDFileFormat.MaskRunGroupRef]];
               loadAddress.highbits ← imagingCommand.addrHighBits;
               loadAddress.lowbits ← maskRunGroupRef.addrLowBits;
               ans.loadAddress ← loadAddress.li;
               IF ans.loadAddress < 0 OR ans.loadAddress + SIZE[PDFileFormat.RunGroup] > handle.loadWords THEN BadLoadReference[SIZE[PDFileFormat.MaskRunGroupRef]];
               runGroup ← private.loadPointer + loadAddress.li;
               ans.pointer ← run ← private.loadPointer + loadAddress.li + SIZE[PDFileFormat.RunGroup];
               WHILE s < runGroup.sSize DO
                  IF INT[run-private.loadPointer] >= handle.loadWords THEN BadLoadReference[SIZE[PDFileFormat.MaskRunGroupRef]];
                  IF LONG[run.fMin] + run.fSize > fSize THEN fSize ← LONG[run.fMin] + run.fSize;
                  IF run.lastRun THEN s ← s + 1;
                  run ← run + SIZE[PDFileFormat.Run];
                  ENDLOOP;
               ans.runCount ← runCount;
               CheckBB[handle, maskRunGroupRef.sMin, maskRunGroupRef.fMin, runGroup.sSize, fSize, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.MaskRunGroupRef]];
               ans.sMin ← maskRunGroupRef.sMin;
               ans.fMin ← ans.fOffset ← maskRunGroupRef.fMin;
               ans.sSize ← runGroup.sSize;
               ans.fSize ← fSize;
               RETURN [ans]
               };
            maskRectangle => {
               ans: CommandBuffer.maskRectangle;
               maskRectangle: PDFileFormat.MaskRectangle;
               ReadBlock[handle, @maskRectangle, SIZE[PDFileFormat.MaskRectangle]];
               CheckBB[handle, maskRectangle.sMin, maskRectangle.fMin, maskRectangle.sSize, maskRectangle.fSize, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.MaskRectangle]];
               ans.sMin ← maskRectangle.sMin;
               ans.fMin ← maskRectangle.fMin;
               ans.sSize ← maskRectangle.sSize;
               ans.fSize ← maskRectangle.fSize;
               RETURN [ans]
               };
            maskTrapezoid => {
               ans: CommandBuffer.maskTrapezoid;
               maskTrapezoid: PDFileFormat.MaskTrapezoid;
               fMin, fMax: INT;
               ReadBlock[handle, @maskTrapezoid, SIZE[PDFileFormat.MaskTrapezoid]];
               fMin ← MIN[maskTrapezoid.fMin, maskTrapezoid.fMinLast];
               fMax ← MAX[LONG[maskTrapezoid.fMin] + maskTrapezoid.fSize, LONG[maskTrapezoid.fMinLast] + maskTrapezoid.fSizeLast];
               CheckBB[handle, maskTrapezoid.sMin, fMin, maskTrapezoid.sSize, fMax-fMin, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.MaskTrapezoid]];
               ans.sMin ← maskTrapezoid.sMin;
               ans.fMin ← maskTrapezoid.fMin;
               ans.fMinLast ← maskTrapezoid.fMinLast;
               ans.sSize ← maskTrapezoid.sSize;
               ans.fSize ← maskTrapezoid.fSize;
               ans.fSizeLast ← maskTrapezoid.fSizeLast;
               RETURN [ans]
               };
            maskRunGroup => {
               ans: CommandBuffer.maskRunGroup;
               maskRunGroup: PDFileFormat.MaskRunGroup;
               runGroup: PDFileFormat.RunGroup;
               s: CARDINAL ← 0;
               runCount: INT ← 0;
               words: INT ← 0;
               fMin: CARDINAL ← LAST[CARDINAL];
               fMax: CARDINAL ← FIRST[CARDINAL];
               run: LONG POINTER TO PDFileFormat.Run;
               ReadBlock[handle, @maskRunGroup, SIZE[PDFileFormat.MaskRunGroup]];
               ReadBlock[handle, @runGroup, SIZE[PDFileFormat.RunGroup]];
               run ← private.bufferOriginPointer;
               private.bufferWordCount ← 0;
               WHILE s < runGroup.sSize DO
                  IF runCount*SIZE[PDFileFormat.Run] >= private.bufferWordCount THEN {
                     -- Read a chunk, assuming one run group per scan line.
                     words: CARDINAL ← (runGroup.sSize-s)*SIZE[PDFileFormat.Run];
                     IF words + private.bufferWordCount > private.bufferWordsAllocated THEN RaisePDError[handle, runGroupTooLong, private.bufferWordCount];
                     ReadBlock[handle, run, words];
                     private.bufferWordCount ← private.bufferWordCount + words;
                     };
                  IF run.fMin < fMin THEN fMin ← run.fMin;
                  IF LONG[run.fMin] + run.fSize > fMax THEN fMax ← LONG[run.fMin] + run.fSize;
                  IF run.lastRun THEN s ← s + 1;
                  runCount ← runCount + 1;
                  run ← run + SIZE[PDFileFormat.Run];
                  ENDLOOP;
               IF runCount*SIZE[PDFileFormat.Run] # private.bufferWordCount THEN ERROR;
               ans.runCount ← runCount;
               ans.loadAddress ← -1;
               IF runCount = 0 THEN {
                  ans.pointer ← NIL;
                  RaisePDWarning[handle, emptyRunGroup, SIZE[PDFileFormat.MaskRunGroup]+SIZE[PDFileFormat.RunGroup]];
                  fMin ← fMax;
                  }
               ELSE {
                  ans.pointer ← private.bufferOriginPointer;
                  CheckBB[handle, maskRunGroup.sMin, fMin, runGroup.sSize, fMax-fMin, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.MaskRunGroup]+private.bufferWordCount]
                  };
               ans.sMin ← maskRunGroup.sMin;
               ans.fMin ← fMin;
               ans.fOffset ← 0;
               ans.sSize ← runGroup.sSize;
               ans.fSize ← fMax;
               RETURN [ans];
               };
            maskSamples => {
               ans: CommandBuffer.maskSamples;
               maskSamples: PDFileFormat.MaskSamples;
               samples: PDFileFormat.SampleArray;
               ReadBlock[handle, @maskSamples, SIZE[PDFileFormat.MaskSamples]];
               ReadBlock[handle, @samples, SIZE[PDFileFormat.SampleArray]];
               CheckBB[handle, maskSamples.sMin, maskSamples.fMin, samples.sSize, samples.fSize, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.MaskSamples]+SIZE[PDFileFormat.SampleArray]];
               ans.loadAddress ← -1;
               {raster: BlockDescription
                  ← ReadLocate[
                     handle,
                     Inline.LongMult[samples.sSize, (samples.fSize+(bitsPerWord-1))/bitsPerWord]
                     ];
                  ans.samples ← PDInterpBitmap.Reshape[
                     raster.pointer,
                     raster.words,
                     [maskSamples.sMin, maskSamples.fMin, samples.sSize, samples.fSize]
                     ! PDInterpBitmap.InsufficientSpace => {
                        RaisePDError[handle, bitmapTooBig, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.MaskSamples]+SIZE[PDFileFormat.SampleArray]];
                        ERROR
                        }
                     ];
                  };
               RETURN [ans];
               };
            colorSamples => {
               ans: CommandBuffer.colorSamples;
               colorSamples: PDFileFormat.ColorSamples;
               samples: PDFileFormat.SampleArray;
               ReadBlock[handle, @colorSamples, SIZE[PDFileFormat.ColorSamples]];
               ReadBlock[handle, @samples, SIZE[PDFileFormat.SampleArray]];
               CheckBB[handle, colorSamples.sMin, colorSamples.fMin, samples.sSize, samples.fSize, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.ColorSamples]+SIZE[PDFileFormat.SampleArray]];
               {raster: BlockDescription
                  ← ReadLocate[
                     handle,
                     Inline.LongMult[samples.sSize, (samples.fSize+(bitsPerWord-1))/bitsPerWord]
                     ];
                  ans.samples ← PDInterpBitmap.Reshape[
                     raster.pointer,
                     raster.words,
                     [colorSamples.sMin, colorSamples.fMin, samples.sSize, samples.fSize]
                     ! PDInterpBitmap.InsufficientSpace => {
                        RaisePDError[handle, bitmapTooBig, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.MaskSamples]+SIZE[PDFileFormat.SampleArray]];
                        ERROR
                        }
                     ];
                  };
               RETURN [ans];
               };
            ENDCASE => {
               RaisePDError[handle, unrecognisedImagingCommand, SIZE[PDFileFormat.Command]]; ERROR};
         };
      controlCommand: PDFileFormat.Command.control => {
         ans: CommandBuffer.stateChange;
         ans.loadChangeStart ← ans.loadChangeLength ← 0;
         IF handle.colorType = none THEN SELECT controlCommand.com FROM
            startImage, deviceCommand, storeLoad, endDocument => NULL;
            ENDCASE => {
               RaisePDError[handle, missingStartImage, SIZE[PDFileFormat.Command]];
               ERROR
               };
         SELECT controlCommand.com FROM
            startImage => {
               startImage: PDFileFormat.StartImage;
               ReadBlock[handle, @startImage, SIZE[PDFileFormat.StartImage]];
               IF startImage.filler # 0 THEN {
                  RaisePDWarning[handle, nonZeroFill, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.StartImage]];
                  };
               IF (LONG[startImage.passBands]+startImage.nBands)*handle.sSizeBand >= handle.herald.imageSSize+handle.sSizeBand
               OR LONG[startImage.fMinPage]+startImage.fSizePage > handle.herald.imageFSize THEN {
                  RaisePDWarning[handle, imageBoundsExceedPageBounds, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.StartImage]];
                  };
               IF handle.colorType # none AND handle.bandNumber < handle.image.nBands THEN
                  RaisePDWarning[handle, tooFewBands, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.StartImage]];
               handle.image ← startImage;
               ans.whatChanged ← imageStart;
               handle.priority ← 0;
               handle.colorType ← ink;
               handle.bandNumber ← 0;
               handle.sMinBand ← startImage.passBands*handle.sSizeBand;
               IF NOT startImage.strip THEN {handle.pass ← handle.pass + 1};
               IF startImage.feed THEN {handle.page ← handle.page + 1; handle.pass ← 1};
               handle.status ← constructingImage;
               RETURN [ans]
               };
            setPriority => {
               priorityLow: PDFileFormat.Priority;
               priority: Environment.LongNumber;
               ReadBlock[handle, @priorityLow, SIZE[PDFileFormat.Priority]];
               priority.highbits ← controlCommand.rest;
               priority.lowbits ← priorityLow;
               handle.priority ← priority.li;
               ans.whatChanged ← priorityChange;
               RETURN [ans]
               };
            setColorInk => {
               handle.colorType ← ink;
               ans.whatChanged ← colorChange;
               RETURN [ans]
               };
            setColorClear => {
               handle.colorType ← clear;
               ans.whatChanged ← colorChange;
               RETURN [ans]
               };
            setColorTile => {
               setColorTile: PDFileFormat.SetColorTile;
               ReadBlock[handle, @setColorTile, SIZE[PDFileFormat.SetColorTile]];
               handle.colorTileLoadAddress ← setColorTile.addr;
               SELECT controlCommand.rest FROM
                  0 => handle.colorType ← opaqueTile;
                  1 => handle.colorType ← transparentTile;
                  ENDCASE => {
                     handle.colorType ← opaqueTile;
                     RaisePDWarning[handle, unknownColorTileFlag, SIZE[PDFileFormat.Command]];
                     };
               ans.whatChanged ← colorChange;
               RETURN [ans];
               };
            endBand => {
               handle.priority ← LAST[INT];
               ans.whatChanged ← priorityChange;
               RETURN [ans];
               };
            endDocument => {
               ans.whatChanged ← documentEnd;
               handle.status ← transmissionComplete;
               RETURN [ans];
               };
            storeLoad => {
               storeLoad: PDFileFormat.StoreLoad;
               loadWordsAllocated: LONG CARDINAL ← private.loadWordsAllocated;
               ReadBlock[handle, @storeLoad, SIZE[PDFileFormat.StoreLoad]];
               IF storeLoad.firstAddress > loadWordsAllocated
               OR storeLoad.firstAddress + storeLoad.wordCount > loadWordsAllocated
               OR storeLoad.firstAddress + storeLoad.wordCount > handle.herald.maxLoadWord THEN
                  {RaisePDError[handle, loadOutOfBounds, SIZE[PDFileFormat.Command]+SIZE[PDFileFormat.StoreLoad]]; ERROR};
               ReadBlock[handle, private.loadPointer+storeLoad.firstAddress, storeLoad.wordCount];
               ans.whatChanged ← loadChange;
               ans.loadChangeStart ← storeLoad.firstAddress;
               ans.loadChangeLength ← storeLoad.wordCount;
               handle.loadWords ← MAX[handle.loadWords, ans.loadChangeStart + ans.loadChangeLength];
               handle.colorTileLoadAddress ← -1;
               RETURN [ans];
               };
            deviceCommand => {
               deviceCommand: CommandBuffer.deviceCommand;
               wordCount: CARDINAL;
               block: BlockDescription;
               ReadBlock[handle, @wordCount, SIZE[CARDINAL]];
               block ← ReadLocate[handle, wordCount];
               IF block.words < wordCount THEN RaisePDError[handle, unexpectedEOF, block.words+SIZE[CARDINAL]];
               deviceCommand.deviceCommandPointer ← block.pointer;
               deviceCommand.deviceCommandWords ← block.words;
               RETURN [deviceCommand];
               };
            ENDCASE => {RaisePDError[handle, unrecognisedControlCommand, SIZE[PDFileFormat.Command]]; ERROR};
         };
      ENDCASE => {RaisePDError[handle, unrecognisedCommandType, SIZE[PDFileFormat.Command]]; ERROR};
   };
   
Error: PUBLIC ERROR [handle: Handle, code: PDInterpBasic.PDErrorCode, wordIndex, wordCount: INT] = CODE;
   
Warning: PUBLIC SIGNAL [handle: Handle, code: PDInterpBasic.PDWarningCode, wordIndex, wordCount: INT] = CODE;
   
RaisePDError: PROC [handle: Handle, code: PDInterpBasic.PDErrorCode, words: INT] = {
   handle.status ← code;
   ERROR Error[handle, code, handle.index-words, words];
   };
   
RaisePDWarning: PROC [handle: Handle, code: PDInterpBasic.PDWarningCode, words: INT] = {
   handle.warningCount ← handle.warningCount + 1;
   handle.status ← code;
   SIGNAL Warning[handle, code, handle.index-words, words];
   };
   
END.