// M E R G E D E L E T E (PREPRESS) // // MergeDelete(f,mflg,...) // Performs merge, supercede, delete, and // compact operations on file f. //Modified by Lyle Ramshaw December 25, 1980 10:49 AM: // Made the new mica size take precedence over the old when // merging Orbit into (Orbit or MultiChars), in case the // sizes differ by one; also added more typeout. Finally, // changed ordering relationship of IX'es so that TEX fonts // work better. //Modified by Lyle Ramshaw September 26, 1980 10:45 AM: // Added Verify flag to the dictionary commands. // Setting the Verify flag set on a dictionary command // will cause each character raster block to be recopied // independently, and in order of increasing character code. // Hence, if some font segments were produced with MetaFont, // the rasters will get sorted. Also, for each character, // the claims about raster dimensions in the CharWidth block // are checked against the claims in the raster block itself. // If there are inconsistent, Swat is called with a sprightly // message (such inconsistencies will send Spruce into Sway, by // the way). If the Verify flag is true, then the Fast flag // is forced to false. //Modified by Lyle Ramshaw September 12, 1980 8:01 PM: // Change storage allocation of linked list to get only as // many words as the particular flavor of IX really needs; // should allow larger dictionaries to be Merged. //Modified by Lyle Ramshaw July 31, 1980 11:16 AM: // Guarantee that family names are in all caps, and all // trailing bytes are zeros. //Modified by Lyle Ramshaw July 6, 1980 10:03 PM: // I put a "Beginning a dictionary command..." message in. //ReWritten completely by Lyle Ramshaw on May 20, 1980 3:58 PM // -fixed bug in Merge that sometimes garbaged fonts produced with // the ReviseWidths flag set. // -there are now two modes of operation for these commands, fast and // slow. In slow mode, the new dictionary is first written on // PrePress.Scratch as before, and then copied. This slow process // is very safe, since some good copy of your input always exists. // The output of a slow operation will always be a compact file as // well, with the data segments in the same order as their index // entries, and contiguous. // In fast mode, the dictionary file will be written in place. // This will go much faster, but has two minor drawbacks: first, // is it more risky, since there is a short "critical section" during // which no valid copy of the dictionary appears on disk. Secondly, // the output file may have holes in it, and its data segments may // be in a different order than the corresponding index entries. get "Ix.dfs" get "AltoFileSys.D" //for STRING stuff get "Streams.d" get "MergeLists.D" // outgoing procedures external [ MergeDelete ] // incoming procedures external [ MergeDeleteOutput //WINDOW WindowSetPosition WindowGetPosition WindowReadBlock WindowWriteBlock WindowRead WindowWrite WindowCopy WindowEnd WindowClose WindowFlush WindowLength //UTIL PrePressWindowInit FSGetX FSPut ReadIX WriteIX CompareIX PrintIX ReadIXTempFile WriteIXTempFile TypeChar CheckParams Scream IllFormat IllCommand //SCAN StrEq StrCop TypeForm //OS OpenFile DoubleAdd Min Max Zero SetBlock MoveBlock ReadCalendar Usc //PressML DoubleAddV //Filops FillIX ] //outgoing statics external [ @ReviseWidths @verifyFlag ] static [ @ReviseWidths=false @verifyFlag=false ] // incoming statics external [ @fam @face @siz @rotation @resolutionx @resolutiony @params @bigfilename ] // internal statics //static // [ // ] // Procedures //MergeDelete -- for the COMPACT, MERGE, SUPERSEDE or DELETE commands //f = 1,2,3 merge or delete segments of SD,CD,WD //mergeFlag= : // 0 Delete segment mentioned in command line. // 1 Standard merge (any stuff in file -f merged into file f) // 2 Supersede (same as merge, but spline widths in -f supercede fixed // versions in f) // 3 Compact: copy input back to input with all data blocks // contiguous, and in the same order as the corresponding IX'es let MergeDelete(f,mergeFlag,careful,oldName,newName;numargs na) be [ if na le 3 then [ newName=-f //little file (SDtemp,ACtemp, or WDtemp) oldName=f //big file: SD,CD,WD, if bigfilename!0 then oldName=bigfilename //or FileName/B ] let DPZero=table [ 0;0 ] let wOld=PrePressWindowInit(oldName) //Dict file (index is 0) let wNew=nil //Source file (index is 1) let maxFile=0 switchon mergeFlag into [ case 0: //delete if na eq 3 then unless CheckParams(gotname) then IllCommand() endcase case 1: //standard merge case 2: //supersede wNew=PrePressWindowInit(newName,false) //Source file if wNew eq 0 then [ Scream("Source file does not exist!") return ] maxFile=1 endcase case 3: //compact careful=true //compacts are always careful endcase default: Scream("Bug in MergeDelete") ] if verifyFlag then careful=true //have to be careful to verify //for delete case, let deleteMe=vec IXLMax FillIX(deleteMe) //Get parameters let foundIt=false let e=vec IXLMax let inNameList=0 //List of all name IX's let maxOldCode=vec 1 //Largest old name code in each file maxOldCode!0=-1; maxOldCode!1=-1 TypeForm("Beginning a dictionary command: ") //Look through both files to get all the name IX'es in one sorted // list (reverse order), recording the old codes as we go. for file=0 to maxFile do [ let wi=(file eq 0)? wOld, wNew WindowSetPosition(wi, DPZero) if WindowEnd(wi) then TypeForm(0,"Warning: An input file has length zero!",0) until WindowEnd(wi) do [ ReadIX(wi, e, true) switchon e>>IXH.Type into [ case IXTypeEnd: break case IXTypeName: [ //first, sanitize the name by making all chars upper case, //and all trailing bytes zero: let newFamName=lv e>>IXN.Name let newFamLen=newFamName>>STRING.length for i=1 to newFamLen do newFamName>>STRING.char^i=UpperCase(newFamName>>STRING.char^i) for i=newFamLen+1 to (size IXN/8)-5 do newFamName>>STRING.char^i=0 //[The 5 non-char bytes in an IXN are: // 2 in header, 2 in Code, 1 as Length of Name.] let p=lv inNameList let newNameFlag=nil //loop through to find IX after which e should be // inserted; inNameList is built in reverse sorted order while true do [ let pn=@p //pn is address of current node if pn eq 0 then [ newNameFlag=true; break ] switchon StrOrder(lv e>>IXN.Name, lv pn>>NNode.ix.Name) into [ case 1: newNameFlag=true; break case 0: [ // family names are equal, set the oldCode newNameFlag=false if pn>>NNode.oldCode^file ne -1 then Scream("Input file contained two name IX'es with the same name!") let c=e>>IXN.Code pn>>NNode.oldCode^file=c maxOldCode!file=Max(c,maxOldCode!file) break ] case -1: p=@p; loop ] ] if newNameFlag then //insert after p [ let n=FSGetX(size NNode/16) n>>NNode.next=@p; @p=n //link it in n>>NNode.oldCode^0=-1; n>>NNode.oldCode^1=-1 let c=e>>IXN.Code n>>NNode.oldCode^file=c maxOldCode!file=Max(c,maxOldCode!file) MoveBlock(lv n>>NNode.ix, e, IXLName) ] ] endcase default: loop ] //of "switchon..." ] //of "until WindowEnd(wi)..." ] //of "for file..." //Reverse the inNameList to put it in increasing order (easier to think // about): Reverse(lv inNameList) //Next step is to assign new codes for all family names, and build // oldcode to newcode translation tables. let codeTable0=FSGetX(maxOldCode!0+1) //+1 since 0 is a legal code let codeTable1=FSGetX(maxOldCode!1+1) SetBlock(codeTable0,-1,maxOldCode!0+1) SetBlock(codeTable1,-1,maxOldCode!1+1) let codeTables=vec 1 codeTables!0=codeTable0 codeTables!1=codeTable1 let p=inNameList let maxNewCode=0; while p do [ maxNewCode=maxNewCode+1 p>>NNode.ix.Code=maxNewCode for file=0 to maxFile do [ let c=p>>NNode.oldCode^file if c ne -1 then (codeTables!file)!c=maxNewCode ] p=@p ] //Next, go through each file, building sorted lists // of data IX'es, and filling in the new codes let inDataList=vec 1 inDataList!0=0; inDataList!1=0 for file=0 to maxFile do [ let wi=(file eq 0)? wOld, wNew WindowSetPosition(wi, DPZero) until WindowEnd(wi) do [ ReadIX(wi, e, true) switchon e>>IXH.Type into [ case IXTypeEnd: break case IXTypeName: loop default: [ let p=lv inDataList!file //fill in new family code value let oldcode=e>>IX.fam let newcode=(codeTables!file)!oldcode if newcode eq -1 then Scream("Input file contained data segment with an undefined family name code!") e>>IX.fam=newcode //loop through to find IX after which e should be // inserted; dataLists are built in reverse sorted order while true do [ let pn=@p //pn points at current DNode if pn eq 0 then break switchon IxOrder(e, lv pn>>DNode.ix) into [ case 1: break case 0: Scream("An input file contains several Data Segments that describe the same font!") // and on into the next case case -1: p=@p; loop ] ] //insert the new data IX in place, with new fam code let n=FSGetX(lDNodeHdr+e>>IXH.Length) n>>DNode.next=@p; @p=n //link it in n>>DNode.file=file MoveBlock(lv n>>DNode.ix, e, e>>IXH.Length) ] ] //of "switchon..." ] //of "until WindowEnd(wi)..." Reverse(lv inDataList!file) ] //of "for file..." FSPut(codeTables!0); FSPut(codeTables!1) //One little detail to fix up at this point: the deleteMe IX // is a pattern that tells us what to delete. But its family code // is garbage, while its family name is stored in "fam". We now // look through the family names once again, to see if we can find // that family name; if we do, we set the code appropriately. if mergeFlag eq 0 then [ p=inNameList while p do [ let thisFam=lv p>>NNode.ix.Name if StrEq(thisFam, fam) then [ deleteMe>>IX.fam = p>>NNode.ix.Code; break ] p=@p ] if p eq 0 then [ TypeForm(0,"Couldn't find specified segment to delete: nothing written.", 0) return ] ] //Now, merge the two dataLists and decide upon what goes into the // final output file, and put that onto outDataList let outDataList=0 while inDataList!0 & inDataList!1 do [ //There is something at the head of each list, so decide who //is smaller; if they are equal, the newer one (from file 1) //wins. let oldNode=inDataList!0 let newNode=inDataList!1 let oldIx=lv oldNode>>DNode.ix let newIx=lv newNode>>DNode.ix let comp=IxOrder(oldIx,newIx) if comp eq 1 then [ // remove newIx from inDataList, and put on outDataList inDataList!1=newNode>>DNode.next newNode>>DNode.next=outDataList outDataList=newNode loop ] if comp eq -1 then [ // remove oldIx from inDataList inDataList!0=oldNode>>DNode.next //now, if deleting, check if this segment gets the axe! if mergeFlag eq 0 & CompareIX(oldIx, deleteMe) then [ foundIt=true; FSPut(oldNode); loop ] //and if Superseding, check again for axe; the // superseding spline width segment, if such exists, // will be smaller than oldIx in the ordering, since // spline blocks have 0 size and 0 rotation. Thus, // we only need search through the outDataList. if mergeFlag eq 2 & oldIx>>IX.Type eq IXTypeWidths then [ let q=outDataList let killIt=false while q do [ let splineIx=lv q>>DNode.ix if splineIx>>IX.Type eq IXTypeWidths & oldIx>>IX.famface eq splineIx>>IX.famface & splineIx>>IX.siz eq 0 then [ killIt=true; break ] q=@q ] if killIt then [ FSPut(oldNode); loop ] ] //else put oldIx on outDataList oldNode>>DNode.next=outDataList outDataList=oldNode loop ] //Well, the heads of the two lists match; //In general, the newIx replaces the oldIx; //but there are complex cases here in case of MultiChars.... //Start out by removing both from the in lists, linking both //to the rest of the outDataList, and pointing outDataList //at the newer. inDataList!0=oldNode>>DNode.next inDataList!1=newNode>>DNode.next oldNode>>DNode.next=outDataList newNode>>DNode.next=outDataList outDataList=newNode //but now decide whether to do funny things depending upon types //if new is Multi, just do the replacement if newIx>>IX.Type eq IXTypeMultiChars then [ FSPut(oldNode); loop ] //if old is Multi, new had better be ORbit, and // the scheme is: ReviseWidths?Push, Smash if oldIx>>IX.Type eq IXTypeMultiChars then [ if newIx>>IX.Type ne IXTypeOrbitChars then Scream("Illegal merge: non-Orbit into MultiChars") //we wil patch the old MultiChars IX in oldNode to bring it // up to snuff; so change outDataList to point there. outDataList=oldNode oldNode>>DNode.file=2 //special code meaning first seg comes // from new file (1) while others come from old (0) if ReviseWidths then //push down width stack [ let n=oldIx>>IXM.numSegs if n ge 4 then Scream("Too many width blocks!") oldIx>>IXM.numSegs = n+1 for i=n to 1 by -1 do [ MoveBlock(lv oldIx>>IXM.segs^(i+1).sa, lv oldIx>>IXM.segs^i.sa, 2) MoveBlock(lv oldIx>>IXM.segs^(i+1).len, lv oldIx>>IXM.segs^i.len, 2) MoveBlock(lv oldIx>>IXM.segs^(i+1).date, lv oldIx>>IXM.segs^i.date, 2) ] //adjust date on the new second segment from infinite future // to right now ReadCalendar(lv oldIx>>IXM.segs^2.date) ] //now, smash first entry with newIx data, and adjust for // different ec, bc, size (might be off by 1) MoveBlock(lv oldIx>>IXM.segs^1.sa, lv newIx>>IX.sa, 2) MoveBlock(lv oldIx>>IXM.segs^1.len, lv newIx>>IX.len, 2) //set the expiration date of this first block to the infinite future SetBlock(lv oldIx>>IXM.segs^1.date, -1, 2) let charOff=(oldIx>>IXM.bc - newIx>>IX.bc)*CharWidthsize oldIx>>IXM.bc=newIx>>IX.bc oldIx>>IXM.ec=newIx>>IX.ec oldIx>>IXM.siz=newIx>>IX.siz let widthLen=vec 1 widthLen!0=0 widthLen!1=(newIx>>IX.ec-newIx>>IX.bc+1)*CharWidthsize for i=2 to oldIx>>IXM.numSegs do [ DoubleAddV(lv oldIx>>IXM.segs^i.sa, -charOff) MoveBlock(lv oldIx>>IXM.segs^i.len, widthLen, 2) ] FSPut(newNode) loop ] //final case to check is Orbit+Orbit+ReviseWidths->Multi unless ReviseWidths & oldIx>>IX.Type eq IXTypeOrbitChars then [ FSPut(oldNode); loop ] if newIx>>IX.Type ne IXTypeOrbitChars then Scream("Illegal merge: non-Orbit into Orbit with ReviseWidths") //well, we now have to build a new MultiChars IX, with two // data blocks, the new and the old let mn=FSGetX(lDNodeHdr+IXLMulti) mn>>DNode.next=oldNode>>DNode.next outDataList=mn mn>>DNode.file=2 //special code for multi-merge let mx=lv mn>>DNode.ix mx>>IXM.Type=IXTypeMultiChars mx>>IXM.Length=IXLMulti mx>>IXM.fam=newIx>>IX.fam mx>>IXM.face=newIx>>IX.face mx>>IXM.siz=newIx>>IX.siz mx>>IXM.bc=newIx>>IX.bc mx>>IXM.ec=newIx>>IX.ec mx>>IXM.rotation=newIx>>IX.rotation mx>>IXM.resolutionx=newIx>>IX.resolutionx mx>>IXM.resolutiony=newIx>>IX.resolutiony mx>>IXM.numSegs=2 MoveBlock(lv mx>>IXM.segs^1.sa, lv newIx>>IX.sa, 2) MoveBlock(lv mx>>IXM.segs^1.len, lv newIx>>IX.len, 2) //now, set the expiration date of this first block to the infinite future SetBlock(lv mx>>IXM.segs^1.date, -1, 2) MoveBlock(lv mx>>IXM.segs^2.sa, lv oldIx>>IX.sa, 2) let charOff=(oldIx>>IX.bc - newIx>>IX.bc)*CharWidthsize DoubleAddV(lv mx>>IXM.segs^2.sa, -charOff) let widthLen=vec 1 widthLen!0=0; widthLen!1=(mx>>IXM.ec-mx>>IXM.bc+1)*CharWidthsize MoveBlock(lv mx>>IXM.segs^2.len, widthLen, 2) ReadCalendar(lv mx>>IXM.segs^2.date) //Now, the new node is all ready, so free the old ones FSPut(oldNode); FSPut(newNode) ] //One list is now exhausted, so handle the other list while inDataList!1 do [ let newNode=inDataList!1 inDataList!1=newNode>>DNode.next newNode>>DNode.next=outDataList outDataList=newNode ] while inDataList!0 do [ let oldNode=inDataList!0 let oldIx=lv oldNode>>DNode.ix inDataList!0=oldNode>>DNode.next //now, if deleting, check if this segment gets the axe! if mergeFlag eq 0 & CompareIX(oldIx, deleteMe) then [ foundIt=true; FSPut(oldNode); loop ] //and if Superseding, check again for axe; the // superseding spline width segment, if such exists, // will be smaller than oldIx in the ordering, since // spline blocks have 0 size and 0 rotation. Thus, // we only need search through the outDataList. if mergeFlag eq 2 & oldIx>>IX.Type eq IXTypeWidths then [ let q=outDataList let killIt=false while q do [ let splineIx=lv q>>DNode.ix if splineIx>>IX.Type eq IXTypeWidths & oldIx>>IX.famface eq splineIx>>IX.famface & splineIx>>IX.siz eq 0 then [ killIt=true; break ] q=@q ] if killIt then [ FSPut(oldNode); loop ] ] oldNode>>DNode.next=outDataList outDataList=oldNode ] Reverse(lv outDataList) //Next, we go through the outDataList and count how many times // each family name is used, since we only want to keep the families // that we have to. let used=FSGetX(maxNewCode+1) Zero(used,maxNewCode+1) p=outDataList while p do [ let ix=lv p>>DNode.ix let fam=ix>>IX.fam used!fam=true p=@p ] //And throw away the names that have no uses let outNameList=0 p=inNameList while p do [ let fam=p>>NNode.ix.Code test used!fam ifso [ //put on outNameList let temp=p>>NNode.next p>>NNode.next=outNameList outNameList=p p=temp ] ifnot [ //flush this useless name let temp=p>>NNode.next FSPut(p) p=temp ] ] Reverse(lv outNameList) //Now its time to start thinking about outputing the new dictionary; //first, check that there is at least one data Ix to be output if mergeFlag eq 0 & foundIt eq false then [ TypeForm(0,"Couldn't find specified segment to delete: nothing written.", 0) return ] if outDataList eq 0 then [ Scream("Empty output file specified: nothing written."); return ] MergeDeleteOutput(outNameList, outDataList, wOld, wNew, careful, oldName) ] //of MergeDelete and Reverse(lvList) be [ //destructively reverse a list // p points at a head of the initial // input list that has been reversed already, // q points at tail of the initial input // still to be reversed. let p=@lvList if p eq 0 then return //empty list let q=@p @p=0 while q do [ let r=@q @q=p p=q q=r ] @lvList=p ] and IxOrder(a,b) = valof [ //first discriminate on type, all Char type are the same let atype=a>>IXH.Type if IsCharsType(atype) then atype=IXTypeChars let btype=b>>IXH.Type if IsCharsType(btype) then btype=IXTypeChars let d=Usc(atype, btype) if d then resultis d //next, on rotation d=Usc(a>>IX.rotation, b>>IX.rotation) if d then resultis d //next on family code value (new code value, so already sorted d=Usc(a>>IX.fam, b>>IX.fam) if d then resultis d let aface=a>>IX.face let bface=b>>IX.face manifest [ Tex=1; Plain=0 ] let afaceType=(aface ge 18?Tex,Plain) let bfaceType=(bface ge 18?Tex,Plain) d=Usc(afaceType,bfaceType) if d then resultis d test afaceType eq Tex ifso [ //next on face, in backwards order d=Usc(bface,aface) if d then resultis d //then on size, with slop of 1 mica let sizDiff=a>>IX.siz-b>>IX.siz if sizDiff gr 1 then resultis 1 if sizDiff ls -1 then resultis -1 ] ifnot [ //next on size, with slop of 1 mica let sizDiff=a>>IX.siz-b>>IX.siz if sizDiff gr 1 then resultis 1 if sizDiff ls -1 then resultis -1 //then on face d=Usc(a>>IX.face, b>>IX.face) if d then resultis d ] //and finally, if CharType, then on resolutions if atype ne IXTypeChars then resultis 0 //atype known to equal btype let aresx,aresy,bresx,bresy=nil,nil,nil,nil test a>>IX.Type eq IXTypeMultiChars then [ aresx=a>>IXM.resolutionx;aresy=a>>IXM.resolutiony ] or [ aresx=a>>IX.resolutionx;aresy=a>>IX.resolutiony ] test b>>IX.Type eq IXTypeMultiChars then [ bresx=b>>IXM.resolutionx;bresy=b>>IXM.resolutiony ] or [ bresx=b>>IX.resolutionx;bresy=b>>IX.resolutiony ] d=Usc(aresx,bresx) if d then resultis d d=Usc(aresy, bresy) if d then resultis d resultis 0 ] and StrOrder(a,b) = valof [ let lena=a>>STRING.length let lenb=b>>STRING.length let len=Min(lena, lenb) for i=1 to len do [ let ca=a>>STRING.char^i let cb=b>>STRING.char^i if ca ls cb then resultis -1 if ca gr cb then resultis 1 ] resultis Usc(lena, lenb) ] and UpperCase(c) = valof [ if $a le c & c le $z then resultis c+$A-$a resultis c ] and IsCharsType(typ) = (typ eq IXTypeChars)%(typ eq IXTypeOrbitChars)%(typ eq IXTypeMultiChars) (1792)