-- AISImpl.mesa
-- Last changed by John Warnock, 23-Nov-81 16:21:11
-- Last Edited by: Stone, August 11, 1983 2:50 pm

DIRECTORY
  AIS,
  AISFormat,
  DCSFileTypes,
  Directory,
  File,
  Heap,
  Inline,
  Space,
  Volume,
  BitBlt,
  Environment;

AISImpl: PROGRAM
IMPORTS Directory, File, Heap, Inline, Space, Volume,BitBlt
EXPORTS AIS = {OPEN AIS;

zone: UNCOUNTED ZONE = Heap.systemZone;
Error: PUBLIC SIGNAL[type: ErrorType] = CODE;
catch: BOOLEAN = TRUE;

wordsPerPage: CARDINAL = Space.wordsPerPage;
aisWordsPerPage: CARDINAL = AISFormat.aisWordsPerPage;
pagesPerAISPage: CARDINAL = aisWordsPerPage/wordsPerPage;
defaultBSize: CARDINAL ← 4*pagesPerAISPage;

bbspace: BitBlt.BBTableSpace;
bbptr: BitBlt.BBptr ← BitBlt.AlignedBBTable[@bbspace];
bbflags: BitBlt.BitBltFlags ← [direction: forward, disjoint: TRUE, disjointItems: FALSE, gray: FALSE, srcFunc: null, dstFunc: null];

ARef: TYPE = LONG POINTER TO ARep;
ARep: TYPE = RECORD [
  fref: FRef ← NIL,
  space: Space.Handle ← Space.nullHandle,	--attributes space
  header: LONG POINTER TO AISFormat.Header ← NIL,
  raster: LONG POINTER TO uca AISFormat.RasterPart ← NIL,
  placement: LONG POINTER TO AISFormat.PlacementPart ← NIL,
  photometry: LONG POINTER TO AISFormat.PhotometryPart ← NIL,
  comment: LONG POINTER TO AISFormat.CommentPart ← NIL
  ];

lastARep: ARep ← [NIL,Space.Create[size: defaultBSize, parent: Space.virtualMemory],NIL,NIL,NIL,NIL];
lastARef: ARef ← @lastARep;

DivUp: PROC[n: LONG CARDINAL, d: CARDINAL] RETURNS[CARDINAL] = INLINE {
  q,r: CARDINAL; [q,r] ← Inline.LongDivMod[n,d];
  RETURN[IF r>0 THEN q+1 ELSE q] };

LongCopy: PROC[from: LONG POINTER, to: LONG POINTER, nwords: LONG CARDINAL] = INLINE {
  wpb: CARDINAL ← 177777B;
  q,r: CARDINAL; [q,r] ← Inline.LongDivMod[num: nwords,den: wpb];
  FOR i: CARDINAL IN [0..q) DO
      Inline.LongCOPY[from: from, to: to, nwords: wpb];
      from ← from+wpb;
      ENDLOOP;
  Inline.LongCOPY[from: from, to: to, nwords: r];
  };
  
CreateFile: PUBLIC PROC[name: LONG STRING, raster: Raster,
  overwrite: BOOLEAN, attributeLength: CARDINAL] RETURNS[f: FRef] = {
  file: File.Capability ← File.nullCapability;
  filesize: File.PageCount ← 0;
  bpp: CARDINAL ← 0; -- bits per pixel
  ppw: CARDINAL ← 0; -- pixels per word
  wpl: CARDINAL ← 0; -- words per scan line
  lines: CARDINAL ← 0; -- raster.scanLength
  pixels: CARDINAL ← 0; -- raster.scanCount
  apages,rpages,bpages: CARDINAL ← 0; -- AIS pages in attribute, raster, and block 
  abase,rbase: File.PageNumber; -- starting file pages

  -- Check for reasonable parameters
  SELECT raster.bitsPerPixel FROM
    0 => { bpp ← 1; ppw ← 16 };
    1,2,4,8,16 => { bpp ← raster.bitsPerPixel; ppw ← 16/bpp };
    3,5,6,7,IN[9..15] => Error[notImplemented]; -- not expected
    ENDCASE => Error[invalidParameter]; -- 0 or out of range
  IF raster.scanCount>0 AND raster.scanLength>0 THEN {
    lines ← raster.scanCount;
    pixels ← raster.scanLength;
    wpl ← DivUp[pixels,ppw];
    }
  ELSE Error[invalidParameter]; -- bad scanCount or scanLength
  apages ← (IF attributeLength=0 THEN 1
  ELSE DivUp[attributeLength,aisWordsPerPage]);
  attributeLength ← aisWordsPerPage*apages;

--compute the number of pages to hold the raster
  IF raster.linesPerBlock>0 THEN {
    blocks: CARDINAL ← DivUp[lines,raster.linesPerBlock]; -- number of blocks
    bwords: LONG CARDINAL ← Inline.LongMult[wpl,raster.linesPerBlock];
    bpages ← DivUp[bwords,aisWordsPerPage]; -- ais pages per block
    rpages ← blocks*bpages;	--total ais pages in raster part
    }
  ELSE {
    rwords: LONG CARDINAL ← Inline.LongMult[wpl,lines];
    rpages ← DivUp[rwords,aisWordsPerPage]	--total ais pages in raster part
    };
  abase ← 1; -- attributes follow leader page
  rbase ← abase + pagesPerAISPage*apages;
  filesize ← rbase + pagesPerAISPage*rpages;
  IF overwrite THEN Directory.DeleteFile[fileName: name ! 
        Directory.Error => SELECT type FROM
        fileNotFound => CONTINUE;
	invalidFileName => IF catch THEN GOTO BadName;
	ENDCASE;
      ];
file ← Directory.CreateFile[fileName: name,
    fileType: DCSFileTypes.tLeaderPage, size: filesize !
      Directory.Error => SELECT type FROM
        fileAlreadyExists => IF catch THEN GOTO OldFile;
	invalidFileName => IF catch THEN GOTO BadName;
	ENDCASE;
      Volume.InsufficientSpace => IF catch THEN GOTO NoRoom;
      ];
  
  file ← File.LimitPermissions[file, File.read + File.write];
  f ← zone.NEW[FRep ← [file: file,  write: TRUE,  abase: abase,  rbase: rbase,  raster: raster,  wordsPerLine: wpl,  buffersize: 0]];
  IF raster.linesPerBlock>0 THEN f.buffersize ← bpages*pagesPerAISPage ELSE SetBufferSize[f];
  IF lastARef.fref#NIL THEN DeleteLastARef[];
  lastARef.fref ← f;
  lastARef.header ← zone.NEW[AISFormat.Header ← [password: AISFormat.aisPassword, attributeLength: attributeLength]];
  Space.Map[space: lastARef.space, window: [file: f.file, base: abase]];
  lastARef.raster ← zone.NEW[uca AISFormat.RasterPart ← [
    part: AISFormat.PartHeader[type: raster, length: SIZE[uca AISFormat.RasterPart]],
    scanCount: raster.scanCount, scanLength: raster.scanLength,
    scanDir: (SELECT raster.scanMode FROM
      ru => 2,
      rd => 3,
      lu => 6,
      ld => 7,
      ul => 9,
      ur => 8,
      dl => 13,
      dr => 12,
      ENDCASE => ERROR),
    samplesPerPixel: 1,
    coding: uca[bitsPerSample: raster.bitsPerPixel, wordsPerScanLine: wpl,
             scanLinesPerBlock: raster.linesPerBlock, paddingPerBlock: raster.paddingPerBlock]
   ]];
  RETURN[f];
  EXITS
    BadName => Error[invalidFileName];
    OldFile => Error[fileAlreadyExists];
    NoRoom => Error[volumeFull];
  };

DeleteFile: PUBLIC PROC[name: LONG STRING] RETURNS[success: BOOLEAN] = {
  success ← TRUE;
  Directory.DeleteFile[name !
    Directory.Error => SELECT type FROM
      fileNotFound => { success ← FALSE; CONTINUE };
      ENDCASE];
  RETURN[success];
  };

OpenFile: PUBLIC PROC[name: LONG STRING, write: BOOLEAN ← FALSE] RETURNS[f: FRef] = {
  file: File.Capability ← Directory.Lookup[
  	fileName: name, 
  	permissions: (IF write THEN File.read + File.write ELSE File.read)
  	! Directory.Error =>SELECT type FROM
  	  fileNotFound => IF catch THEN GOTO NotFound;
  	  invalidFileName => IF catch THEN GOTO BadName;
	  ENDCASE;
	];
  scanmode: ScanMode ← ru;
  apages: Space.PageCount;
  f ← zone.NEW[FRep];
  f.file ← file;
  f.abase ← FirstPage[f.file];
  ReadAttributes[f];	--sets up lastARef, and maps the file
  apages ← lastARef.header.attributeLength/wordsPerPage;
  f.rbase  ← f.abase + apages;
  scanmode ← SELECT lastARef.raster.scanDir FROM 
      2 => ru,
      3 => rd,
      6 => lu,
      7 => ld,
      9 => ul,
      8 => ur,
      13 => dl,
      12 => dr,
      ENDCASE => rd;
  f.raster ← zone.NEW[AIS.RasterPart ←
      [scanCount: lastARef.raster.scanCount, scanLength: lastARef.raster.scanLength,
       scanMode: scanmode, bitsPerPixel: lastARef.raster.bitsPerSample,
       linesPerBlock: lastARef.raster.scanLinesPerBlock, paddingPerBlock: lastARef.raster.paddingPerBlock]];
  f.wordsPerLine ← lastARef.raster.wordsPerScanLine;
  f.write ← write;
  IF lastARef.raster.scanLinesPerBlock>0 THEN {
    bwords: LONG CARDINAL ←
      Inline.LongMult[lastARef.raster.wordsPerScanLine,lastARef.raster.scanLinesPerBlock];
    bpages: LONG CARDINAL ← DivUp[bwords,aisWordsPerPage]; -- ais pages per block
    temp: LONG CARDINAL ← bpages*pagesPerAISPage;
    IF Inline.HighHalf[temp]=0 THEN f.buffersize  ← Inline.LowHalf[temp] ELSE ERROR;
    }
  ELSE SetBufferSize[f];
  RETURN[f];
  EXITS
    BadName => Error[invalidFileName];
    NotFound => Error[fileNotFound];
  };

CloseFile: PUBLIC PROC[f: FRef] = {
--If the file is mapped,  writes out the file and cleans up the local data structures
  IF f=lastARef.fref THEN DeleteLastARef[];
  zone.FREE[@f];	--dangle, dangle...
  };

SetBufferSize: PROC[f: FRef] = {
  rsize: CARDINAL ← DivUp[Inline.LongMult[f.wordsPerLine,f.raster.scanCount],wordsPerPage];
  f.buffersize ← IF rsize<defaultBSize THEN rsize ELSE defaultBSize;
  };

--read and write the attribute parts  
ReadComment: PUBLIC PROC[f: FRef, s: LONG STRING] RETURNS[BOOLEAN] = {
  len: INTEGER;
  ReadAttributes[f];
  IF lastARef.comment=NIL THEN RETURN[FALSE];
  len ← LOOPHOLE[lastARef.comment.chars[0]];
  len ← MIN[s.length, len];
  IF len<=0 THEN RETURN[FALSE];
  FOR i: INTEGER IN [0..len) DO s[i] ← lastARef.comment.chars[i+1]; ENDLOOP;
  RETURN[TRUE];
  };

WriteComment: PUBLIC PROC[f: FRef, s: LONG STRING] = {
  len: CARDINAL ← s.length;
  ReadAttributes[f];
  IF ~f.write THEN Error[readOnlyFile];
  len ← MIN[256,len];
  IF lastARef.comment#NIL THEN zone.FREE[@lastARef.comment];
  lastARef.comment ← zone.NEW[AISFormat.CommentPart[len+1]];
  lastARef.comment.part ← [type: comment, length: SIZE[AISFormat.CommentPart[len+1]]];
  lastARef.comment.chars[0] ← LOOPHOLE[len];
  FOR i: CARDINAL IN [0..len) DO lastARef.comment.chars[i+1] ← s[i]; ENDLOOP;
  };
  

ReadPlacement: PUBLIC PROC[f: FRef, placement: Placement] RETURNS[BOOLEAN] = {
  ReadAttributes[f];
  IF lastARef.placement=NIL THEN RETURN[FALSE];
  placement↑ ← [
    xLeft: lastARef.placement.xLeft,
    yBottom: lastARef.placement.yBottom,
    xWidth: lastARef.placement.xWidth,
    yHeight: lastARef.placement.yHeight];
  RETURN[TRUE];
  };

WritePlacement: PUBLIC PROC[f: FRef, placement: Placement] = {
  ReadAttributes[f];
  IF ~f.write THEN Error[readOnlyFile];
  IF lastARef.placement=NIL THEN lastARef.placement ← zone.NEW[AISFormat.PlacementPart];
  lastARef.placement↑ ← [
    part: [type: placement, length: SIZE[AISFormat.PlacementPart]],
    xLeft: placement.xLeft,
    yBottom: placement.yBottom,
    xWidth: placement.xWidth,
    yHeight: placement.yHeight];
  };

ReadRaster: PUBLIC PROC[f: FRef, raster: Raster] = {
  ReadAttributes[f];
  IF lastARef.fref.raster=NIL THEN ERROR;
  raster↑ ← f.raster↑; 
  };

ReadPhotometry: PUBLIC PROC[f: FRef, photometry: Photometry] RETURNS[BOOLEAN] = {
  ReadAttributes[f];
  IF lastARef.photometry=NIL THEN RETURN[FALSE];
  photometry↑ ← [
    signal: lastARef.photometry.signal,  
    sense: lastARef.photometry.sense,  
    scaleType: lastARef.photometry.scaleType,
    pointA: lastARef.photometry.pointA,
    pointB: lastARef.photometry.pointB,
    pointC: lastARef.photometry.pointC,
    spotType: lastARef.photometry.spotType,
    spotWidth: lastARef.photometry.spotWidth,
    spotLength: lastARef.photometry.spotLength,
    sampleMin: lastARef.photometry.sampleMin,  
    sampleMax: lastARef.photometry.sampleMax,
    histogramLength: lastARef.photometry.histogramLength];
  RETURN[TRUE];
  };

ReadHistogram: PUBLIC PROC[f: FRef, histogram: Histogram] RETURNS[BOOLEAN] = {
  l: INTEGER;
  ReadAttributes[f];
  IF lastARef.photometry=NIL OR lastARef.photometry.histogramLength<=0 THEN RETURN[FALSE];
  l ← MIN[lastARef.photometry.histogramLength,histogram.len];
  FOR i: INTEGER IN [0..l) DO
    histogram[i] ← lastARef.photometry.histogram[i];
    ENDLOOP;
  RETURN[TRUE];
  };

WritePhotometry: PUBLIC PROC[f: FRef, photometry: Photometry, histogram: Histogram ← NIL] = {
  len: INTEGER ← 0;
  ReadAttributes[f];
  IF ~f.write THEN Error[readOnlyFile];
--Error check the lengths
  IF histogram#NIL THEN {len ← histogram.len; IF len#photometry.histogramLength THEN ERROR}
  ELSE IF photometry.histogramLength>0 THEN ERROR;
--allocate the lastARef fields according to the length
  IF lastARef.photometry#NIL THEN zone.FREE[@lastARef.photometry];
  lastARef.photometry ← zone.NEW[AISFormat.PhotometryPart[len]];
  lastARef.photometry.part ← [type: photometry, length: SIZE[AISFormat.PhotometryPart[len]]];
  lastARef.photometry.signal ← photometry.signal;
  lastARef.photometry.sense ← photometry.sense;
  lastARef.photometry.scaleType ← photometry.scaleType;
  lastARef.photometry.pointA ← photometry.pointA;
  lastARef.photometry.pointB ← photometry.pointB;
  lastARef.photometry.pointC ← photometry.pointC;
  lastARef.photometry.spotType ← photometry.spotType;
  lastARef.photometry.spotWidth ← photometry.spotWidth;
  lastARef.photometry.spotLength ← photometry.spotLength;
  lastARef.photometry.sampleMin ← photometry.sampleMin;
  lastARef.photometry.sampleMax ← photometry.sampleMax;
  lastARef.photometry.histogramLength ← photometry.histogramLength;
  IF histogram#NIL THEN  FOR i: INTEGER IN [0..len] DO
    lastARef.photometry.histogram[i] ← histogram[i];
    ENDLOOP;
  };
 


-- Window operations --

OpenWindow: PUBLIC PROC[f: FRef,
  firstScan: CARDINAL ← 0, lastScan: CARDINAL ← LAST[CARDINAL],
  firstPixel: CARDINAL ← 0, lastPixel: CARDINAL ← LAST[CARDINAL]]
  RETURNS[w: WRef] = {
  bref: BRef;
  wpl,ppw: CARDINAL;
  bref ← zone.NEW[BRep];
  IF lastScan=LAST[CARDINAL] THEN lastScan ← f.raster.scanCount;
  IF lastPixel=LAST[CARDINAL] THEN lastPixel ← f.raster.scanLength;
  lastScan ← MIN[f.raster.scanCount-1,lastScan];
  lastPixel ← MIN[f.raster.scanLength-1,lastPixel];
  IF firstScan>lastScan THEN Error[badWindow];
  IF firstPixel>lastPixel THEN Error[badWindow];
  bref↑ ← [space: Space.Create[f.buffersize,Space.virtualMemory],
    addr: NIL, first: 0, last: 0];
  ppw ← IF f.raster.bitsPerPixel=0 THEN 16 ELSE 16/f.raster.bitsPerPixel;
  wpl ← DivUp[lastPixel-firstPixel+1,ppw];
  w ← zone.NEW[WRep ← [fref: f, bref: bref,
    firstScan: firstScan, lastScan: lastScan, firstPixel: firstPixel, lastPixel: lastPixel,
    wordsPerLine: wpl, pixelsPerWord: ppw]];
  MapBuffer[w,w.firstScan];
  };

CloseWindow: PUBLIC PROC[w: WRef] = {
  Space.Delete[w.bref.space];
  zone.FREE[@w.bref];
  zone.FREE[@w];	--dangle, dangle
  };
  
--scan is absolute scan number
MapBuffer: PROC[w: WRef, scan: CARDINAL ← 0] = INLINE
 {IF scan>w.lastScan THEN Error[outOfRange];
  IF w.bref.addr#NIL AND scan IN [w.bref.first..w.bref.last] THEN RETURN; --already mapped in
  GMBuff[w,scan];};
  
GMBuff:PROC [w: WRef, scan: CARDINAL ← 0] = 
 {page,offset,lnum,loff: CARDINAL;
  --compute which file page
  [page,offset] ← Inline.LongDivMod[num: Inline.LongMult[scan,w.fref.wordsPerLine], den: wordsPerPage];
  IF w.bref.addr#NIL THEN Space.Unmap[w.bref.space];	--first time, isn't mapped
  Space.Map[w.bref.space,[w.fref.file,w.fref.rbase+page]];
  --compute the number of the first scanline in the buffer
  [lnum,loff] ← Inline.LongDivMod[num: offset, den: w.fref.wordsPerLine];
  w.bref.addr ← Space.LongPointer[w.bref.space]+loff;
  w.bref.first ← scan-lnum;
  w.bref.last ← w.bref.first + Inline.LongDiv[num: Inline.LongMult[w.fref.buffersize,wordsPerPage]-loff, den: w.fref.wordsPerLine] - 1;
  };

GetWindowParams: PUBLIC PROC[w: WRef]
    RETURNS[firstScan: CARDINAL, lastScan: CARDINAL, firstPixel: CARDINAL, lastPixel: CARDINAL] = {
  firstScan ← w.firstScan;
  lastScan ← w.lastScan;
  firstPixel ← w.firstPixel;
  lastPixel ← w.lastPixel;
  };	

MinBufferSize: PUBLIC PROC[w: WRef] RETURNS[length: CARDINAL] = {RETURN[w.wordsPerLine]};

EndOfWindow: PUBLIC PROC[w: WRef] RETURNS[BOOLEAN] = {RETURN[w.nextScanLine=(w.lastScan-w.firstScan)]};

--Buffer: TYPE = LONG DESCRIPTOR FOR ARRAY OF WORD;

ReadLine: PUBLIC PROC[w: WRef, buffer: Buffer, line: INTEGER ← -1] = {
  XFerLine[w,buffer,line,TRUE];
  };

WriteLine: PUBLIC PROC[w: WRef, buffer: Buffer, line: INTEGER ← -1] = {
  XFerLine[w,buffer,line,FALSE];
  };

XFerLine: PROC[w: WRef, buffer: Buffer, line: INTEGER ← -1, read: BOOLEAN] = {
  woff,poff,absline: CARDINAL;
  word: LONG POINTER;
  IF w.wordsPerLine>buffer.length THEN Error[bufferTooSmall];
  IF line=-1 THEN line ← w.nextScanLine;
  absline ← CARDINAL[line]+w.firstScan;
  IF absline > w.lastScan THEN Error[outOfRange];
  MapBuffer[w,absline];
  [woff,poff] ← Inline.LongDivMod[num:w.firstPixel,den: w.pixelsPerWord];
  word ← w.bref.addr+Inline.LongMult[w.fref.wordsPerLine,(absline-w.bref.first)]+woff;
  IF poff=0 THEN {
    IF read THEN LongCopy[from: word, to: buffer.addr, nwords: w.wordsPerLine]
    ELSE LongCopy[from: buffer.addr, to: word, nwords: w.wordsPerLine]
    }
  ELSE {
    src,dst: BitBlt.BitAddress;
    bit: CARDINAL ← IF w.fref.raster.bitsPerPixel=0 THEN poff ELSE poff*w.fref.raster.bitsPerPixel;
    bufferwidth: CARDINAL ← w.wordsPerLine*16;
    filebpl: CARDINAL ← w.fref.wordsPerLine*16;
    IF read THEN {
      src ← [word: word, bit: bit];
      dst ← [word: buffer.addr, bit: 0];
      bbptr↑ ← [dst: dst, dstBpl: bufferwidth, src: src, srcDesc: [srcBpl[srcBpl: filebpl]],
    	       width: bufferwidth, height: 1, flags: bbflags];
      }
    ELSE {
      dst ← [word: word, bit: bit];
      src ← [word: buffer.addr, bit: 0];
      bbptr↑ ← [dst: dst, dstBpl: filebpl, src: src, srcDesc: [srcBpl[srcBpl: bufferwidth]],
    	       width: bufferwidth, height: 1, flags: bbflags];
      };
    BitBlt.BITBLT[bbptr];
    };
  w.nextScanLine ← MIN[line+1,w.lastScan];
  };
  

ReadLines: PUBLIC PROC[w: WRef, buffer: Buffer, line: INTEGER ← -1, count: CARDINAL ← 1]
  RETURNS[numberRead: CARDINAL] = {
  RETURN[XFerLines[w,buffer,line,count,TRUE]];
  };
  
WriteLines: PUBLIC PROC[w: WRef, buffer: Buffer, line: INTEGER ← -1, count: CARDINAL ← 1]
  RETURNS[numberWritten: CARDINAL] = {
  RETURN[XFerLines[w,buffer,line,count,FALSE]];
  };

XFerLines: PROC[w: WRef, buffer: Buffer, line: INTEGER ← -1, count: CARDINAL ← 1, read: BOOLEAN]
  RETURNS[numberRead: CARDINAL] = {
  temp:  LONG CARDINAL;
  maxline: LONG CARDINAL ← 0;
  numberRead ← 0;
  temp←Inline.LongMult[w.wordsPerLine,count];
  IF buffer.addr=NIL OR temp>buffer.length THEN Error[bufferTooSmall];
  IF line=-1 THEN line ← w.nextScanLine;
  IF CARDINAL[line]+w.firstScan > w.lastScan THEN Error[outOfRange];
  maxline ← w.firstScan+(buffer.length/w.wordsPerLine)-1;
  IF w.wordsPerLine=w.fref.wordsPerLine THEN {
    first: CARDINAL ← line+w.firstScan;
    lastscan,last: CARDINAL ← first+count-1;
    to: LONG POINTER ← buffer.addr;
    from: LONG POINTER ← NIL;
    nlines,nwords: CARDINAL ← 0;
    lastscan ← MIN[lastscan,w.lastScan];
    IF lastscan>maxline THEN Error[bufferTooSmall];
    UNTIL numberRead>= count DO
      MapBuffer[w,first];
      last ← MIN[lastscan,w.bref.last];
      nlines ← last-first+1;
      nwords ← nlines*w.wordsPerLine;
      from ← w.bref.addr+Inline.LongMult[(first-w.bref.first),w.wordsPerLine];
      IF read THEN LongCopy[from: from, to: to, nwords: nwords]
      ELSE LongCopy[from: to, to: from, nwords: nwords];
      numberRead ← numberRead+nlines;
      first ← last+1;
      to ← to+nwords;
      ENDLOOP;
    }
  ELSE {
    tbuffer: Buffer ← [w.wordsPerLine, Heap.MakeNode[zone, w.wordsPerLine]];
    to: LONG POINTER ← buffer.addr;
    tbuffer.length ← w.wordsPerLine;
    FOR i: CARDINAL IN[0..count) DO
    IF CARDINAL[line]+w.firstScan>maxline THEN Error[bufferTooSmall]; 
    IF read THEN {
      ReadLine[w,tbuffer,line ! Error => IF type=outOfRange THEN EXIT ELSE ERROR];
      LongCopy[from: tbuffer.addr, to: to, nwords: tbuffer.length];
      }
    ELSE {
      LongCopy[from: to, to: tbuffer.addr, nwords: tbuffer.length];
      WriteLine[w,tbuffer,line ! Error => IF type=outOfRange THEN EXIT ELSE ERROR];
      };
    line ← line+1;
    to ← to+w.wordsPerLine;
    numberRead ← numberRead+1;
    ENDLOOP;
    };
  w.nextScanLine ← MIN[line+numberRead,w.lastScan];
  };

Line1: TYPE = RECORD[PACKED SEQUENCE COMPUTED CARDINAL OF [0..1B]];
Line2: TYPE = RECORD[PACKED SEQUENCE COMPUTED CARDINAL OF [0..3B]];
Line4: TYPE = RECORD[PACKED SEQUENCE COMPUTED CARDINAL OF [0..17B]];
Line8: TYPE = RECORD[PACKED SEQUENCE COMPUTED CARDINAL OF [0..377B]];
Line16: TYPE = RECORD[PACKED SEQUENCE COMPUTED CARDINAL OF WORD];

ReadSample: PUBLIC PROC[w: WRef, line,pixel: CARDINAL] RETURNS[value: CARDINAL] = {
  lineaddr: LONG POINTER;
  absline: CARDINAL ← line+w.firstScan;
  IF absline # w.currline 
   THEN
   {IF absline > w.lastScan THEN Error[outOfRange];
   MapBuffer[w,absline];
   w.currline←absline;
   w.clineaddr ← w.bref.addr+Inline.LongMult[(absline-w.bref.first),w.fref.wordsPerLine];
   }
   ELSE
   {IF pixel+ w.firstPixel > w.lastPixel THEN Error[outOfRange];};
  lineaddr←w.clineaddr;
  SELECT w.fref.raster.bitsPerPixel FROM
    0,1 => RETURN[LOOPHOLE[lineaddr,LONG POINTER TO Line1][pixel]];
    2 => RETURN[LOOPHOLE[lineaddr,LONG POINTER TO Line2][pixel]];
    4 => RETURN[LOOPHOLE[lineaddr,LONG POINTER TO Line4][pixel]];
    8 => RETURN[LOOPHOLE[lineaddr,LONG POINTER TO Line8][pixel]];
    16 => RETURN[LOOPHOLE[lineaddr,LONG POINTER TO Line16][pixel]];
    ENDCASE => Error[notImplemented];
  w.nextScanLine ← MIN[line+1,w.lastScan];
  };
  
WriteSample: PUBLIC PROC[w: WRef, value: CARDINAL, line,pixel: CARDINAL] = {
  lineaddr: LONG POINTER;
  absline: CARDINAL ← line+w.firstScan;
  IF absline # w.currline 
   THEN
   {IF absline > w.lastScan THEN Error[outOfRange];
   MapBuffer[w,absline];
   w.currline←absline;
   w.clineaddr ← w.bref.addr+Inline.LongMult[(absline-w.bref.first),w.fref.wordsPerLine];
   }
   ELSE
   {IF pixel+ w.firstPixel > w.lastPixel THEN Error[outOfRange];};
  lineaddr←w.clineaddr;
  SELECT w.fref.raster.bitsPerPixel FROM
    0,1 => LOOPHOLE[lineaddr,LONG POINTER TO Line1][pixel] ← value;
    2 => LOOPHOLE[lineaddr,LONG POINTER TO Line2][pixel] ← value;
    4 => LOOPHOLE[lineaddr,LONG POINTER TO Line4][pixel] ← value;
    8 => LOOPHOLE[lineaddr,LONG POINTER TO Line8][pixel] ← value;
    16 => LOOPHOLE[lineaddr,LONG POINTER TO Line16][pixel] ← value;
    ENDCASE => Error[notImplemented];
  w.nextScanLine ← MIN[line+1,w.lastScan];
  };
  
ReadAttributes: PROC[f:FRef] = {OPEN AISFormat;
  addr: LONG POINTER ← NIL; -- address of first word of attributes
  words: CARDINAL ← 0; -- number of words read from attributes
  awords: CARDINAL ← apages*Environment.wordsPerPage;
  header: LONG POINTER TO AISFormat.Header ← NIL;
  part: LONG POINTER TO AISFormat.PartHeader ← NIL;
  raster: LONG POINTER TO uca AISFormat.RasterPart ← NIL;
  placement: LONG POINTER TO AISFormat.PlacementPart ← NIL;
  photometry: LONG POINTER TO AISFormat.PhotometryPart ← NIL;
  comment: LONG POINTER TO AISFormat.CommentPart ← NIL;
  string: LONG STRING ← NIL;
  apages: Space.PageCount;

  IF lastARef.fref#NIL THEN
    IF f.file = lastARef.fref.file THEN RETURN	--we just did the read
    ELSE DeleteLastARef[];
  lastARef.fref ← f;
  
  Space.Map[space: lastARef.space, window: [file: f.file, base: f.abase]];
  addr ← Space.LongPointer[lastARef.space];
  header ← addr;
  IF header.password#AISFormat.aisPassword THEN Error[invalidFile]; -- wrong password
  IF (header.attributeLength MOD AISFormat.aisWordsPerPage)=0 THEN {
    apages ← header.attributeLength/wordsPerPage;
    IF apages > Space.GetAttributes[lastARef.space].size THEN {
      Space.Delete[lastARef.space];
      lastARef.space ← Space.Create[size: apages, parent: Space.virtualMemory];
      Space.Map[space: lastARef.space, window: [file: f.file, base: f.abase]];
      };
     }
  ELSE Error[invalidFile]; -- illegal attribute length
  lastARef.header ← zone.NEW[Header ← header↑];
  words ← SIZE[AISFormat.Header];
  DO -- read the parts
    part ← addr + words; words ← words + part.length;
    IF words>header.attributeLength THEN
      Error[invalidFile]; -- part overflows end of attribute section
    SELECT part.type FROM
      nil => EXIT; -- no more parts
      raster => IF raster=NIL
        THEN {raster ← LOOPHOLE[part]; lastARef.raster ← zone.NEW[uca RasterPart ← raster↑]}
	ELSE Error[invalidFile]; -- multiple raster parts
      placement => IF placement=NIL
        THEN {placement ← LOOPHOLE[part]; lastARef.placement ← zone.NEW[PlacementPart ← placement↑]}
	ELSE Error[invalidFile]; -- multiple placement parts
      photometry => IF photometry=NIL THEN {
      	hlen: INTEGER;
	photometry ← LOOPHOLE[part];
	hlen ← photometry.histogramLength;
	IF hlen<0 THEN hlen ← 0;
	lastARef.photometry ← zone.NEW[PhotometryPart[hlen] ← photometry↑];
	}
	ELSE Error[invalidFile]; -- multiple photometry parts
      comment => IF comment=NIL
        THEN {comment ← LOOPHOLE[part]; lastARef.comment ← zone.NEW[CommentPart ← comment↑]}
	ELSE Error[invalidFile]; -- multiple comment parts
      ENDCASE => Error[invalidFile]; -- unknown part type
    ENDLOOP;
  IF raster=NIL THEN Error[invalidFile]; -- raster part missing
  IF raster.samplesPerPixel#1 THEN ERROR; -- Can't handle multiple samples per pixel
  IF raster.codingType#uca THEN ERROR; -- Coding type not UCA
  };

FirstPage: PROC[file: File.Capability] RETURNS[File.PageNumber] = {
 RETURN[1];	--should check with the Directory stuff
 };
 
DeleteLastARef: PROC = {OPEN lastARef;	--using: fref,header,space,raster,placement,photometry,comment
  IF fref=NIL THEN ERROR;
  IF fref.write THEN WriteLastARef[];
  --don't free the FRef.  Someone has a hold of it!
  IF header#NIL THEN {zone.FREE[@header]; header ← NIL};
  IF raster#NIL THEN {zone.FREE[@raster]; raster ← NIL};
  IF placement#NIL THEN {zone.FREE[@placement]; placement ← NIL};
  IF photometry#NIL THEN {zone.FREE[@photometry]; photometry ← NIL};
  IF comment#NIL THEN {zone.FREE[@comment]; comment ← NIL};
  Space.Unmap[space];
  lastARef.fref ← NIL;
  };

WriteLastARef: PROC = {OPEN Inline,lastARef;	--using: header,space,raster,placement,photometry,comment
--assumes the file is mapped to lastARef.space
--if all elements are NIL, this is a NOOP
  to: LONG POINTER ← Space.LongPointer[space];
  IF header#NIL THEN
      {LongCOPY[from: header, to: to, nwords: SIZE[AISFormat.Header]]; to ← to+SIZE[AISFormat.Header]};
  IF raster#NIL THEN
      {LongCOPY[from: raster, to: to, nwords: raster.part.length]; to ← to+raster.part.length};
  IF placement#NIL THEN
      {LongCOPY[from: placement, to: to, nwords: placement.part.length]; to ← to+placement.part.length};
  IF photometry#NIL THEN 
      {LongCOPY[from: photometry, to: to, nwords: photometry.part.length]; to ← to+photometry.part.length};
  IF comment#NIL THEN
      {LongCOPY[from: comment, to: to, nwords: comment.part.length]; to ← to+raster.part.length};    
  };
-- Initialization

bbptr↑ ← [dst: [word: NIL,bit:0], dstBpl: 0, src: [word: NIL,bit: 0], srcDesc: [srcBpl[srcBpl: 0]], width: 0, height: 0, flags: bbflags]
}.

-- Last changed by Maureen Stone, 17-Nov-81 17:32:46  fixed overwrite in CreateFile
-- Last changed by Maureen Stone, 17-Nov-81 13:59:17  fixed write header in DeleteLastARef, and changed linesPerBlock in CreateFile
-- Last changed by Gordon Hamachi,  5-Nov-81 18:47:08, to fix overflow on first check in XferLines
-- Last changed by Gordon Hamachi,  5-Nov-81 18:38:18, to fix the computation of rsize in SetBufferSize
-- Last changed by Maureen Stone, 5-Nov-81 17:26:28
-- Last changed by John Warnock, 23-Nov-81 16:20:02, changed MapBuffer,ReadSample, and WriteSample