TweetFollow Us on Twitter

Text Rotation
Volume Number:4
Issue Number:2
Column Tag:Advanced Mac'ing

Looking At Text From a Different Angle

By John D. Olsen,

RPS, Bryan, TX

John D. Olsen is a Registered Public Surveyor in the State of Texas with approx 14 years experience in the field of Land Surveying and Civil Engineering. He is currently attending Texas A&M University in pursuit of a B.S. degree in Electrical Engineering. He is currently working at TAMU in the Veterinary Pathology Dept. at the School of Veterinary Medicine. He supports and maintains a network of Macs (approximately 35 machines). He is working on two vertical market applications for the Macintosh. The first is a Scatchard Plot/Ligand Binding program. The second ‘on-going’ project is a CAD program for Land Surveyors and Civil Engineers involved in land division, development and/or surveying.

“Yes, But Can You Type Upside Down?”

It seems that no matter what we get in the way of library routines, ROM code, standard packages, etc there is always something that we think ‘just should have been included’ as a part of the standard Toolbox routines. It seems one of the favorite things to pick on with the Macintosh is the TextEdit routines in ROM.

Although Apple did ‘upgrade’ the TextEdit routines with the introduction of the new machines most of us feel it is still lacking in several areas. The biggest complaint is usually the ‘limit’ of 32K per record. Apple did extend TextEdit to handle styles in an integral manner, color text, and with several other goodies that tended to make our lives a little easier.

One of the things I have heard few complaints about and which has admittedly not always been very high up on my wish list is the ability to place rotated text on the screen. It would be especially nice if I were able to type it in the same familiar manner that I use in editors, word processors, etc I am involved in putting together some vertical market applications and have run up against this problem. For example, I need to be able to put labels on graphs and/or bodies of text in a CAD program. This text necessarily must be able to be read from the right side of the page.

I learned early in my engineering courses that I should always completely define the problem and the givens before even attempting to begin a solution (to be perfectly honest I think we really all begin to try to solve a problem AS we are defining it but it sounds so much more professional to approach it in such an orderly fashion. Back to the problem at hand).

The problem I had required that the text be rotated in even increments of 90 degrees. This is therefore a very specific solution as opposed to general rotation or what I refer to as a ‘free rotation’ routine; that is, it is not constrained by any particular angular increments as far as the user is concerned.

Fig. 1 Our example allows you to edit rotated text!

I normally take the attitude that the general solution is the best solution for the obvious reason of re-usability of the code, adaption to other similar problems and so on. In this case I decided that performance was of the utmost importance and had the feeling that this problems solution would be CPU intensive. I also had a premonition from the start that this program probably would not perform to my satisfaction if written in a high level language.

Pascal is my language of choice when writing sizable programs. I felt rather anxious about the fact that at least a portion of this program might have to be written in Assembly language. Since my only experience to date was in debugging, I had a lot to learn. I was one of those naive souls who only read Assembly code when trying to track down some innocuous piece of code that had the audacity to crash MY PROGRAM and this only after everything else had failed. It seldom helped me solve my problem but as I found the exposure was of great value.

The Plan

The first thing I did was ask around to see if anyone I knew had already solved or attempted to solve this problem. I do not like to re-invent the proverbial wheel unless forced to do so. I talked to Greg and Scott of the MacHax™ Group and they were not aware of any work to date (at least not any ‘non-proprietary’ stuff) but did express interest and a great deal of encouragement. I checked the bulletin boards and found only one small piece of code that was completely written in Pascal with an unusual approach that used Copybits as the work horse. I tested it and found it took about 7-8 seconds to rotate a 3-4 sq. in. bitMap. This seemed to confirm my feeling that the core routine would have to be in Assembly Language. I also threw away the code I had downloaded.

Having my problem fairly well defined I came up with several possible (?) solutions to the problem. I wanted this routine to behave similarly to DrawString if I could, so that I could simulate standard TextEdit routines by building on this basic module.

The first solution that came to mind was a complete rewrite of the TextEdit routines but done in such a way that the user could select the orientation of the text to be displayed/edited. This had the added advantage of allowing me to add to MY TextEdit the things that I had always wanted. It sounded nice but it was quite a large feat. It also did more (a lot more) than I need it to do. It was like knocking out ant hills with atomic bombs - just a little overkill. I decided to file this idea in the back of my in case the solution I did come up with would be able to be built upon as the ‘ultimate TextEdit’.

The next idea was to use offscreen bitMaps and an Assembly Language routine to rotate my text and then CopyBits it back to the desired position. This seemed like the best approach and was chosen as the method I would use. This had the added advantage of being able to rotate graphic objects as well.

Greg Marriott and I discussed pre-rotating fonts as an alternative method to rotating the bitMaps on the fly (FONTs can be thought of as a long narrow bitMap). I could then develop a method for handling the new family of characters. This is an idea well worth exploring but I chose the former solution instead as I felt it was the simpler of the two and had the advantage of being smaller and more general.

What I Did On My Summer Vacation

My programs are written in Pascal with an external procedure written in Assembly Language to do the nitty-gritty of the rotation. I have included several examples of the program to show how it was tested during development along with a couple of ideas for implementation. The programs all function in the same manner although they do different things.

The original program that I wrote for testing the routine simply rotates an ICON. There was actually a predecessor to this program that generated a stripe pattern and rotated it. I quickly found this to be of little use since the pattern was so simple. I was lucky enough to get all but the most trivial problems worked out and got what I expected when I FINALLY got the Assembly code done. ( More on that later.)

The second program puts up a modal dialog box that acts as an editor. It has two or four windows depending on the version you choose to run. The two window version works better on the MacPlus as the performance of the four window degrades. The illusion of putting up text in all windows simultaneously works fine in the four window version on the Mac II or an accelerated machine such as my MacPlus with a Novy 020/881 at 16mhz and 4 megs of wide (32 bit) memory.

The third program is an example of using the routine the way I needed it to fill my needs for labels on my graphing and CAD programs. It also has an example of putting in picComments for high resolution output devices. I will discuss the flow and operation of this program. The others are included as examples only.

How It Works

I prefer the MPW environment for my work although occasionally I do use Lightspeed Pascal for some of my stuff (usually something small/quick). Most of this program is quite straightforward and is all in MPW’s Pascal and Assembly Language. I don’t use fancy tricks, bend the rules or rewrite anything that I don’t have to. See listing 1 to follow along here.

I’ll begin at the top of the ‘MAIN’. The first thing we do is to Init our environment. This is only done once and some of this is not needed if you use Lightspeed Pascal but is a good thing to keep in practice (it is almost always the same). Then we need to set up our window to draw in, and our grafport. You should notice I’m paranoid about my code getting broken so I check for the screen size and menuBar height (thru screenBits and the low memory global: mBarHeightGlobal = $BAA) then move in from those so that it will work on all systems. I like to try to follow as many of the guidelines as I can, and if someone at Apple (or in this publication) can show me a ‘bullet proof’ way of doing things you can bet I’ll take advantage of it. One of the nice things about MPW is which I keep a bunch of ‘re-usables’ in my Worksheet (like the code to set the window size) that I can copy and paste in every time I start a new application.

The Big Garbonza

At this point I set the font, size and face through toolbox calls. This would more than likely be done either thru a menu, pop-up or dialog box in a real implementation but for now it is hard coded to keep things simple. Now we are ready for our call to then big garbonza, the one we’ve all been waiting for ‘xDrawString’. This is it - this is where it is all really done. (That is except for the Assembly Language stuff and yes I promise I’ll get to that if you will just hold your horses a little longer !)

Ok - now back to ‘xDrawString’. I decided when I designed the Assembly Language code that I would pass a bitMap to the routine and get one back and that’s all. No more parameters. The first stuff is to prepare for this call. After the call we copyBits our new rotated string (bitMap), set up the stuff for the picComments in case we need to copy to MacDraw or print to LaserWriter and cleanup. Thats it.

Counting Hairs

Let’s look at it again in detail now.

First we get the FontInfo so we can use some of the code in the record. I need the length and height of the minimum size bitMap into which the string we want to draw will fit. We call StringWidth() to get the length in pixels. It doesn’t get much easier than that.

I then compute the height which will be equal to the ascent height of the font plus twice the descent height. We use twice the descent height so that we get ‘white space’ all the way around and don’t clip any danglies off. I then set my bounding rectangle up with a call to setRect. (These toolbox names are SO handy/meaningful, don’t you think ?) and calculated sizes. Now I have a rect but what I really want is to stuff it into the bitMap record that I am initializing. I can do this with a simple assignment to the ‘bounds’ field.

Now I call a neat little routine named ‘NewBitMapClear’ (another one of those re-usables) that will calculate my rowBytes and size for me. It then gets a pointer to the new block of memory (if available) and clears it.

newPtrClear FUNC EXPORT
 IMPORT SAVERETA1
 
 MOVE.L (SP)+,A1
 MOVE.L (SP)+,D0
 _NewPtrclear
 MOVE.L A0,(SP)
 JMP  SAVERETA1
 
 ENDF

The clearing (setting all bits to zero) is quite significant and will be evident why it is important later. Voila our sourceMap is set up.

Next we do some book keeping for later. We get the address of our current grafPort so we can get back to it later. We then go thru the normal gyrations of setting up our offscreen grafPort (with the bitMap we just created) and selecting it. We also make sure the text attributes are the same for this grafPort as our original grafPort since we set our desired text style for it.

Draw That String - Tote That Bale

We move from the origin of our new grafPort ( 0,0 ) to (0, myFontInfo.ascent ) that is we move along he left edge of our bitMap down (in a positive direction) to the ascent height of our font which puts us at the baseline. Then we finally draw our string (keep in mind that this is all ‘offscreen’)

OK, we’ve got our offscreen bitMap set up so now I will go ahead and set our port back to where we started:

SetPort( origPort );

Let’s recap to this point:

1. We init all the managers

2. Set up our offscreen bitmap/grafport

3. We copy text attributes from orig->new

4. Move to where we want the string

5. Draw that string offscreen

Ok so we draw our string offscreen, call our assembly language stuff to wave the magic wand. Then we copyBits it to the correct (Original) grafport. The rest of the example code is dedicated to setting up picComments and clean-up. Since these items have been covered in detail before I’ll leave them alone for now. Just keep in mind that picComments have become a very important part of all code that has anything to do with images that may be printed. This is so we can take advantage of the high resolution output devices primarily (LaserWriters, plotters, transfer to object drawing programs).

Off To The Land of The Unknown

Now everything was primed and ready to go so we could call our Assembly Language routine. This is the routine that does ALL the real work. It must make a copy of our bitMap ONE BIT AT A TIME (this is not completely true but we’ll get to that in a minute). Obviously this is not the kind of thing to do in a high level language because of the tremendous overhead.

There was only one small complication to doing this procedure. I had NEVER written in assembly language.

With this challenge in front of me and a desire to really understand what I was looking at when I opened up TMON, Nosy or a dumpcode file I was off to my library. I had read all the standards for Macintosh programing and remembered the section in the back Scott Knaster’s book “How to Write Macintosh Software” on how to read assembly code. I got it out read it again and realized that I understood a lot of the individual instructions and what they did, the various addressing modes, etc The thing I didn’t understand was HOW/WHERE TO START.

Banging My Head Against The Wall

I decided the only thing to do was to enlist some help.

I went to Greg Marriott of the MacHax™ group and explained my predicament. He agreed to help me. We spent about an hour talking about the same stuff I had just gone over. He also told me that he had a similar problem when he sat down to write his first Assembly language routine. We spent the next hour writing a small procedure and talking. That hour was the most enlighting time I had spent in my quest to write this code (or at least it seemed that way).

When I get to a point in my programing where it seems I just can’t figure out how to do something it usually helps to either go on to something else or quit for a while. That is good unless you don’t really know where to start.

I hope going thru my code will help someone else get past the point where I was hung up. I will discuss the general design and action of this routine and then go thru it step by step. If you have a lot of experience in writing Assembly code the step by step will be quite boring ( I suggest you skip on to the next section after Watching the Grass Grow).

How & Why of My Asm

The reason we use assembly code is that we want control (in this case at the bit level) without all the overhead. When you get control you are in COMPLETE CONTROL. The Mac is not nearly as forgiving when working at this low level. That means instead of hitting a pot hole in the road you’ve hit a tree or gone off a cliff.

This routine maps all the bits in the sourceMap to the destMap while rotating the map 90 degrees in a clockwise direction.

I pass the sourceMap and destMap to the asm routine. The routine expects both of these to have been declared as bitMaps but only the sourceMap to have been initialized.

I handle the math involved in computing the new rectangle. The width of the sourceMap is a multiple of words (16 bits or two bytes) wide. That is the definition of rowBytes. The destMaps width, which is not necessarily on an even word, will be set to an even word. Rather than deal with end conditions I leave the height of the DestMap (which is the same as the width of the sourceMap) on an even word boundary.

In the worst case we could end up rotating an extra 15 bit wide strip along the right edge. By doing this extra work we save an extra test for each bit. For small bitMaps the actual ‘extra’ area is small anyway. For large bitMaps the ‘extra’ area is a small percentage of the whole.

I setup this bitMap each time thru the routine, therefore the programmer is responsible for disposal of this temporary map. I chose to do this math and memory allocation in the Assembly routine. If the user doesn’t like this it would be trivial to remove and handle in high level.

The rotation routine starts at the top left corner of the sourceMap and moves across a word at a time.

I map to the lower left corner of the destMap and move up a word at a time. This is done with 16 words for each word of the destMap. After I reach the top I move over a bit and do it again.

I test each bit for being set and only if true do I get a word from memory and set its corresponding bit. This is possible because I clear memory when I allocate the space for the bitMap. This saves moving memory unnecessarily. This is the most costly portion of the routine because if all bits were set in the sourceMap we would have to get each word from memory and return it 16 times.

Its as simple as that. Nothing real complicated is being done here; it is just time consuming.

The Better Mousetrap

This can all be done in much smaller increments in registers. This will save all the moving in and out and in and out of each word. It should make the performance increase by at least an order of magnitude.

I know of a fellow Aggie here in Bryan/College Station who is working on such a routine and article and look forward to reading about his approach. Until then I will use my stuff.

Watching The Grass Grow

As promised I will go thru the code a chunk at a time for those who wish to avoid the anguish that I went thru.

First we have another little procedure (actually a Function) we should discuss ‘ newPtrClear’. The only real difference in this and newPtr is that we call this register based routine with the ‘clear’ parameter. This saves the step of calling EraseRect or something similar to set all bits to zero. This is the code that does that for us:

 newPtrClear FUNC EXPORT
 
 IMPORT SAVERETA1
 
 MOVE.L (SP)+,A1
 MOVE.L (SP)+,D0
 _NewPtrclear
 MOVE.L A0,(SP)
 JMP  SAVERETA1
 
 ENDF

Lets talk about format since we are getting down to the nitty-gritty. The first thing I should mention about the format of our text file is that it is ‘line based’. That is each command/operation is on a single line as opposed to a language like pascal that is procedure based.

The first column is for labels only. Period. Nothing else. If you want to get screwed up put something else there and watch your error list grow out of sight. There is one exception, that being comments, which can go anywhere.

There are several things to remember about comments. The first is that they start with a semi-colon. The next is that they don’t have a terminating character so although they can be on the same line they can’t be embedded. The only other thing I want to mention is that with Assembly code you should comment everything. Use them excessively. It will save you a lot of time later.

I will get off my soap box now and get on with it. From time to time I will mention some of the things I ‘discovered’ or found to be pitfalls.

The first part of the code sets up our constants, stackframe and saves our registers so we can get back to the state we were in when we started. This is something you will almost always do.

DestMap EQU 4+4  ; offset to dest bitmap ^
SourceMap EQU DestMap + 4 ; offset to source bitmap ^
;
; create local stack frame, and save some registers
; make room for dest rect to be passed back
;
 link a6,#0 ; set up a stack frame 
 movem.ld3-d7/a2-a5,-(sp) ; save reg pascal may need

The next couple of lines get us pointing to the bitMaps by getting the address off the stack. This is done by using a displacement off the stack pointer.

 move.l SourceMap(a6),a1  ; point to the source BitMap
 move.l DestMap(a6),a2    ; point to the dest BitMap

Next we will compute the size of our destMap in preparation for filling out its record and getting it some memory. Notice the logical shifts instead of divides and multiplies when working with powers of 2. This save several cycles.

 move.w bounds+bottom(a1),d7; get bottom and put in d7
 sub.w  bounds+top(a1),d7 ; bot - top = height, put in d7
 move.w d7,d0    ; save a copy of height                       
 ; rnd = (height+15)/16
 addi.w #15,d7   ; add 15 to the ht, put in d7
 lsr    #4,d7    ; divide by 16
 lsl    #1,d7    ; multiply by 2 = rowBytes of                 
 ; dest bitMap
 lsr    #1,d0    ; divide height by 2
 move.w d0,d2    ; make a copy of height/2 for later
 move.w bounds+top(a1),d5; get ‘top’ of source rect
 add.w  d0,d5    ; d5 now contains vert component              
 ; of ctr pt
 ; compute the width of src rect & ; put in d4
 move.w bounds+right(a1),d3 ; get right and put in d3
 sub.w  bounds+left(a1),d3; right - left = width, put          
 ; in d3
 lsr    #1,d3    ; divide width by 2
 move.w d3,d1    ; make a copy of width/2 for later
 move.w bounds+right(a1),d4 ; get rt of source rect
 sub.w  d3,d4    ; d4 now contains horiz                       
 ; component of ctr pt
 ; compute ‘top’
 move.w d5,d3  ; get a copy of ctrPt.v and put
 ;  in d3
 sub.w  d1,d3  ; ctrPt.v - width/2 = ‘top’ in d3
 swap d3; move ‘top’ to hiword
 ; compute ‘left’
 move.w d4,d3  ; get a copy of ctrPt.h and put                 
 ; in loword of d3
 sub.w  d2,d3  ; ctrPt.h - height/2 = ‘left’ 
 ; in loword of d3
;
; we now have ‘top’ in hiword & ‘left’ in loword d3
;
 ; compute ‘bottom’
 move.w d5,d6  ; get a copy of ctrPt.v in of d3
 add.w  d1,d6  ; ctrPt.v + width/2 = ‘bottom’ in d3
 swap d6; move ‘bottom’ to hiword
 ; compute ‘right’
 move.w d4,d6  ; get a copy of ctrPt.h and put 
 ; in loword d6
 add.w  d2,d6  ; ctrPt.h + height/2 = ‘right’ 
 ; in loword d6

Note that some of the blocks of comments have been stripped out of the code so we can repeat it here without getting too carried away.

I have already assigned the rect data directly. That is, I put in the structure directly since we can do that here and avoid the overhead of a trap call. This is something that some programmers do not take advantage of. There are two reasons I can think of for this.

The first is that the _SetRect trap is much smaller (fewer lines of code). It also doesn’t take much thinking to put it in.

The second and probably more important is that the traps tend to make the code more readable ( easier to understand).

The next section of code fills in the rest of the bitMap structure for us.

;
; assign data to dest bitMap structure directly
;
 move.w d7,rowBytes(a2)   ; put in rowBytes direct for         
 ; dest bitMap
 move.l d3,bounds+topLeft(a2) ; put the coord pair in          
 ; direct
 move.l d6,bounds+botRight(a2); put the coord pair in          
 ; direct
 move.w d7,d0    ; put rowbytes in d0
 mulu rowBytes(a1),d0; mul rowBytes src*dest = d0
 lsl    #3,d0    ; multiply d0 by 8 for size of                
 ; dest bitMap
 _NewPtrclear    ; allocate size of dest bitMap                
 ; (a2 pts to it) 
 move.l a0,baseaddr(a2)   ; move contents of a0 to             
 ; baseAddr field
 ; of dest bitMap structure

Notice that I repeated the call to _NewPtr with the clear parameter in the code. We could have done this with a subroutine but unrolling the code again saves some time and this is a small piece of code.

The next chunk of code actually gets to the heart of the matter. This is where we start doing all the swapping and testing of bits. We are all set up and ready to roll.

 lea  localVars,a0
;
; initialize d0 to contain the pointer to the srcMap
;
 move.w rowbytes(a1),d0 ; get rowbytes of srcMap
;
; get regs pointing to bits
;
 move.l (a1),a1  ; a1 pts to BITS of src bitMap
 move.l (a2),a3  ; a3 pts to BITS of dest bitMap

I chose to let the compiler compute the addresses of my local variables for me. This does not cause any additional overhead as it is all done at compile time. I load the address of the beginning of my variables into a0 and access all of them via offsets off this register. I could have used one of the nicer features of MPW that allows the user to build records. The effect is the same. I chose to roll my own because it was something I already understood.

 clr.l  d1; clear d1 
 move.w d0,d1    ; load rowBytes of srcMap
 ; (rowBytes of srcMap = ht of     ; destMap
 lsl.w  #3,d1    ; multiply by 8 to get ht of                  
 ; destMap when rot’d
 mulu.w d7,d1    ; multiply by rowbytes(destMap) =             
 ; #words in
 sub.w  d7,d1    ; destMap less one row
 add.l  a3,d1    ; add the computed offset to the              
 ; baseAddr of destMap
 move.l d1,lowerLeft-localVars(a0) ; init lowerLeft

The previous section of code computes the lower left corner of the destination bitMap. This is the ‘master reference point’ for all of the addressing computations for the destMap. All addresses are computed as an offset from this corner. This offset is in the form of a bit and rowbytes times the number of rows. This is not computed each time thru though. It is kept up with counters which are needed anyway.

;
; init wordCount to zero 
;
 move.w #0,wordCount-localVars(a0) 
 ; set wordCount to  ;zero before we start
;
; init colmLoop counter
;
 move.w d7,d2    ; get rowBytes(dest)
 lsr    #1,d2    ; divide by 2 - d2 now contains the           
 ; outer counter
 move.w d2,colmCount-localVars(a0) ; put the max               
 ; value away for reference
 moveq.l#0,d2    ; set d2 to zero since we use it
 ;  for the counter
colmLoop
;
; this is the outside loop
;
 move.w #15,d4   ; init colmBitLoop counter to 15
 move.l lowerLeft-localVars(a0),currentWord-localVars(a0)
 ; init currentWord
colmBitLoop

The previous section of code sets up the labels for all the loops and initializes the counters. This is where it got confusing to write. I made a rookie mistake in my first attempts to code this inner section. I tried to code straight thru instead of setting up the loops,then going back and filling them in from the inside out. Once I changed my approach to looking at a couple of lines at a time I had it licked.

We finally get to the point next where we are testing bits and getting a word out of the destMap if true. We then set the appropriate bit and put the word back. Again we only get the word if we need to SET a bit because they are all cleared to start with. This is critical to the performance.

We work completely thru a source word at a time - easy.

 move.w (a1),d3  ; get a word to rotate into d3
 addq.w #2,wordCount-localVars(a0) ; add 1 for ea word 
 moveq.l#0,d6    ; clear d6 
 move.w #15,d6   ; initialize the inside (bit) loop            
 ; counter
 
innerLoop
;
; we test each bit (15->0 ie. lt to rt) if it is set we worry
; about going out and setting it in destMap otherwise we
; just increment the counters and test the next bit (cause 
; we cleared them all at init)
;
 btst d6,d3 ; test bit pointed to in the
 ; current src word
 ; by the inner (bit) loop counter
 beq    NoBitToChange
 sub.l  d7,currentWord-localVars(a0) ; subtract                
 ; rowBytes(destMap) from
 ; current word
 dbra d6,innerLoop ; keep looping (thru bits) till d6 = -1
 addq.l #2,a1    ; point to the next word in srcMap

The next piece of code is the bookkeeping section that keeps us moving thru both of the bitMaps in synch. The only thing tricky is the slightly unorthodox use of a dbra which actually is used more as an independent counter than a loop. That is we have a label up top that we get to from two places and the second place gets control only when we exit the dbra ‘counter’.

 movea.lcurrentWord-localVars(a0),a2 
 ; get addr of currentWord
 move.w (a2),d5  ; get currentWord
 bset d4,d5 ; set proper bit in currentWord
 move.w d5,(a2)  ; put the currentWord word back
 ; into destMap
noBitToChange
doneWithSrcWord
 cmp    wordCount-localVars(a0),d0 
 ; cmp wordCount to rowBytes-are we done with a row ?
 bne    rowLoop  ; no - do another word
doneWithRow
 move.w #0,wordCount-localVars(a0) 
 ; reset wordCount to zero
 move.l lowerLeft-localVars(a0),currentWord-localVars(a0)      
 ; re-init currentWord  
dbra  d4,colmBitLoop ; go thru the bits (15->0) in the
 ; destMap - then exit and do it
 ; again for rowBytes/2 times
doneWithDestWord
 addq.l #2,lowerLeft-localVars(a0) ; this moves us             
 ; into the next
 ; ‘column’ of bits in the destMap
 addq.w #1,d2    ; add 1 to the colmLoop counter
 cmp    colmCount-localVars(a0),d2 ; are we done yet ?
 bne    colmLoop ; no - do it again

The only thing left to do is our clean-up. Then after our clean-up code is the stuff to set up the local variables (pretty straight forward stuff).

Has The Jury Reached a Verdict?

With the introduction of the new machines life has once again gotten just a little more complicated. We used to be able to think of a one to one relationship for bits and pixels. Life was so easy then (HA!). Anyway, now with the advent of color we have to be careful and make sure we write for the correct machines.

What am I getting to ? This code will work only on the old machines and for the ‘old color model’. It will be relatively easy to adapt to the eight bit color mode in the new model but thats another article.

If I were going to give someone some tips before they started to write their first assembly language routine as I just did I would say ‘Make it work first’. Now that sounds rather paradoxical I know. What I mean by it is that you should start small and work up. Make you code do the bare minimum first then after it is working enhance it.

I’d Like To Thank The Academy

If you’re real lucky you’ll have someone around to ask questions of. I was lucky. I had Greg Marriott. He forced me to learn this stuff - No spoon feeding here. I thank him for that. I know I asked some really dumb questions sometimes. I would also be remiss if I did’nt mention Scott Boyd. He came up with the idea for the quasi-editor rot4Edit in a brainstorming session.

If you don’t have anyone around or would like to comment on my code (please don’t flame me to bad - this was my FIRST) or if you have a neat idea that I could implement with this code contact me at MCI JOLSEN or AppleLink D0795.

Use this code if you like it and let me know what you do with it.

Listing 1 rotString.p
PROGRAM rotString;

{
••••••••••••••••••••••••••••••••••
 rotString by John D. Olsen
 for
 MacTutor Magazine
 © Jan 1988
••••••••••••••••••••••••••••••••••
}

USES
    {$LOAD MemQukOSToolPack}
    Memtypes, Quickdraw, OSIntf, ToolIntf, PackIntf;
    {$LOAD}
 
CONST
     picDwgBeg     = 130;
     picDwgEnd     = 131;
     TextBegin     = 150;
     TextEnd     = 151;
     StringBegin    = 152;
     StringEnd     = 153;
     TextCenter      = 154;

TYPE
     TTxtPicRec = packed Record
                  tJus: Byte;
                  tFlip: Byte;
                  tRot: integer;
                  tLine: Byte;
                  tCmnt: Byte;
                  end;
     TTxtPicPtr = ^TTxtPicRec;
     TTxtPicHdl = ^TTxtPicPtr;

     TTxtCenter = Record
                  y, x: Fixed;
                  end;
     TTxtCenPtr = ^TTxtCenter;
     TTxtCenHdl = ^TTxtCenPtr;

VAR
 thehandle : handle;
 windowRect, sourceRect, destRect : rect;
 myWindow : windowPtr;
 sourceMap, destMap, tempMap : BitMap;
 myLabelStr : str255;
 

FUNCTION NewPtrClear( theSize: size ) : Ptr; EXTERNAL;

PROCEDURE Rotate( srcMap, destMap : BitMap ); EXTERNAL;

PROCEDURE NewBitMapClear( VAR theBitMap : BitMap );
 BEGIN
 WITH theBitMap, bounds DO
 BEGIN
 rowBytes := ((right - left + 15) DIV 16) * 2;
 baseAddr := NewPtrClear(rowBytes * (bottom - top));
 IF MemError <> noErr then baseAddr := NIL;
 END;
 END;{NewBitMapClear}

  PROCEDURE OpenWindow;

    CONST
      mBarHeightGlobal = $BAA;

    VAR
      screen : rect;
      mBarHeight : Integer;
      MemoryPtr : ^Integer;

    BEGIN
      MemoryPtr := Pointer( mBarHeightGlobal );
      mBarHeight := MemoryPtr^;
   screen := screenBits.bounds;
   
 SetRect( windowRect, screenBits.bounds.left + 5, screenBits.bounds.top 
+ mBarHeight + 25, screenBits.bounds.right- 5, screenBits.bounds.bottom 
- 5 );  
 myWindow := NewWindow( NIL, windowRect, ‘Text Rotation’ ,true , documentProc, 
windowPtr( -1 ), false, longint( 0 ));
 SetPort( myWindow );
   
    END;{OpenWindow}
 

PROCEDURE xDrawString( myLabelStr: str255 );

 VAR
 myFontInfo: FontInfo;
 strWidth, mapLength :INTEGER;
 mapHeight, xOffset, yOffset : INTEGER;
 sourceMap, destMap : BitMap;
 sourceRect, destRect, myClipRect : Rect;
 MyRgn: RgnHandle;
 origPort, offScrGrafPort : GrafPtr;
 textHandle, centerHandle : Handle;
 Tx, Ty: fixed;

 BEGIN
 GetFontInfo(myFontInfo);
 strWidth := StringWidth(myLabelStr);
 mapLength := ((strWidth - 1) div 16 + 1) * 16;
 mapHeight := myFontInfo.ascent + 2 * (myFontInfo.descent );
 SetRect(sourceRect, 0, 0, mapLength, mapHeight);  { set up our offscreen 
bitMap }
 sourceMap.bounds := sourceRect;
 NewBitMapClear( sourceMap );

 GetPort(origPort);
 { create new grafPort to make sure things stay clean }
 offScrGrafPort := GrafPtr(NewPtr(sizeof(GrafPort)));
 OpenPort(offScrGrafPort);
 offScrGrafPort^.portRect := sourceMap.bounds;
 SetPortBits(sourceMap);
 WITH offScrGrafPort^ DO
 BEGIN
 txFont := origPort^.txFont;
 txSize := origPort^.txSize;
 txFace := origPort^.txFace;
 END;

 GetFontInfo( myFontInfo );   
 { draw the string we want rotated into our temp bitMap }
 MoveTo( 0, myFontInfo.ascent);
 DrawString( myLabelStr );

 SetPort(origPort);{ set our port back to the original }
 Rotate( sourceMap, destMap );  
 {  rotate the offscreen bitmap }

 destRect := destMap.bounds;{ going to draw it ? }
 WITH destRect DO  { offset to 10,10 }
 OffsetRect( destRect, -left, -top );
 OffsetRect( destRect, 10, 10 );

 {set up picComments }
 textHandle := NewHandle(sizeof(TTxtPicRec));
 centerHandle := NewHandle(sizeof(TTxtCenter));
 WITH TTxtPicHdl(textHandle)^^ DO
 BEGIN
 tJus := 2;   {center}
 tFlip := 0;    {no flip}
 tRot := 270;   {rotate 270 degrees}
 tLine := 1;    {single spacing}
 tCmnt := 0;
 END;

 HLock(centerHandle);
 WITH TTxtCenHdl(centerHandle)^^, destRect DO
 BEGIN
 Ty := FixRatio(bottom - top, 2);
 Tx := FixRatio(right - left, 2);
 xOffset := - strWidth div 2;
 yOffset := (myFontInfo.ascent - myFontInfo.descent) div 2;
 x := FixRatio(-xOffset, 1);
 y := FixRatio(-yOffset, 1);
 MoveTo(left + HiWrd(Tx) + xOffset, top + HiWrd(Ty) + yOffset);
 END;

 PicComment(picDwgBeg, 0, nil);
 PicComment(TextBegin,  sizeof(TTxtPicRec), textHandle);
 PicComment(TextCenter, sizeof(TTxtCenter), centerHandle);
 HUnlock(centerHandle);
 DisposHandle(centerHandle);
 DisposHandle(textHandle);

 SetRect(myClipRect, 0, 0, 0, 0);  { set to ‘nada’ }
 ClipRect(myClipRect);

 { this just puts the string into the picture }
 DrawString(myLabelStr);

 { now set the clipRect back and draw the rotated bitmap }
 ClipRect(origPort^.portRect);

 CopyBits(destMap, origPort^.portBits, destMap.bounds, destRect, srcOr, 
nil);
 
 PicComment(TextEnd, 0, nil);
 PicComment(picDwgEnd, 0, nil);

 {clean up our mess}
 ClosePort(offScrGrafPort);
 DisposPtr(ptr(offScrGrafPort));
 DisposPtr(sourceMap.baseAddr);
 DisposPtr(destMap.baseAddr);
 SetClip(MyRgn);
 DisposeRgn(MyRgn);
 END;{xDrawString}

 
{ • • • • • • • •    M A I N   • • • • • • • • • • }
BEGIN
 FlushEvents(everyEvent,0);
 InitGraf(@thePort);
 InitFonts;
 InitWindows;
 InitMenus;
 InitDialogs(NIL);
 InitCursor;
 
 OpenWindow;

 TextFont(Geneva);
 TextSize(12);
 TextFace([]);
 myLabelStr := ‘Looking at text from a different angle’;
 xDrawString( myLabelStr );
 REPEAT UNTIL button;
 DisposeWindow( myWindow );
END.
Listing 2 Rotate.a Assembly Code
••••••••••••••••••••••••••••••••••
 Rotate by John D. Olsen
 for
 MacTutor Magazine
 © Jan 1988
••••••••••••••••••••••••••••••••••

 INCLUDE‘SysEqu.a’ 
 INCLUDE‘Traps.a’
 INCLUDE‘QuickEqu.a’
 
newPtrClear FUNC EXPORT
 
 IMPORT SAVERETA1
 
 MOVE.L (SP)+,A1
 MOVE.L (SP)+,D0
 _NewPtrclear
 MOVE.L A0,(SP)
 JMP  SAVERETA1
 
 ENDF


Rotate PROC EXPORT
;----------------------------------------------------------
;
; PROCEDURE Rotate (sourceMap, destMap : bitMap );
;
; given a source and destination bitmap rotate it CCW 90° 
; bitmap can be of any size, align on a word boundaries (v & h)
;
;----------------------------------------------------------
;a0 = result of _newPtr call| d0 = size of bitMap 
;a1 = ptr to src bitMap   | d1 = width/2  (source)
;a2 = ptr to dest bitMap  | d2 = height/2 (source)
;a3 = dest bits  | d3 = left | top
;a4 = copy of dest bits   | d4 = ctrPt.h
;a5 = not used   | d5 = ctrPt.v
;a6 = not used   | d6 = right | bottom
;a7 = not used   | d7 = rowBytes of dest bitMap
;----------------------------------------------------------
; ***************************************************
; **
; *Initialize destination bitMap (destMap), etc    *
; **
; ***************************************************
; parameter offsets
;
DestMap EQU 4+4  ; offset to dest bitmap ^
SourceMap EQU DestMap + 4 ; offset to source bitmap ^
;
; create local stack frame, and save some registers
; make room for dest rect to be passed back
;
 link   a6,#0    ; set up a stack frame (no bytes)
 movem.ld3-d7/a2-a5,-(sp) ; save registers for pascal

 move.l SourceMap(a6),a1  ; point to the source BitMap
 move.l DestMap(a6),a2  ; point to the dest BitMap
;
; compute height of source BitMap rounded up to nearest word
;
 move.w bounds+bottom(a1),d7; get bottom and put in d7
 sub.w  bounds+top(a1),d7 ; bottom - top = height, in d7
 move.w d7,d0    ; save a copy of height for later
 ; rnd = (height+15)/16, follows:
 addi.w #15,d7   ; add 15 to the height, put in d7
 lsr    #4,d7    ; divide by 16
 lsl    #1,d7    ; multiply by 2 = rowBytes of dest bitMap
;
; d7 now contains rowBytes of dest bitMap
;
; now compute the destRect for dest bitMap
;
; compute center point of Source rect
; note the center points are not necess. the
; same in the vert dir. relative to srcMap
; because of rounding that follows (i.e. height
; of srcMap is dir. prop. to RowBytes of destMap)

 lsr    #1,d0  ; divide height by 2
 move.w d0,d2  ; make a copy of height/2 for later
 move.w bounds+top(a1),d5 ; get ‘top’ of source rect
 add.w  d0,d5  ; d5 now contains vert component of ctr pt
 ; compute the width of src rect & put in d4
 move.w bounds+right(a1),d3 ; get right and put in d3
 sub.w  bounds+left(a1),d3; right - left = width,in d3
 lsr    #1,d3  ; divide width by 2
 move.w d3,d1  ; make a copy of width/2 for later
 move.w bounds+right(a1),d4 ; get right of source rect
 sub.w  d3,d4  ; d4 now contains horiz component of ctr pt
;
; now get center point and compute bounds for dest bitMap
; height and width are reversed now for the 2 bitMaps
;
; left  :=ctrPt.h - height/2
; top :=ctrPt.v - width/2
; right :=ctrPt.h + height/2
; bottom:=ctrPt.v + width/2
; SetRect(rect, left, top, right, bottom) put data in direct to avoid 
the overhead of a trap call
;
; compute ‘top’
 move.w d5,d3  ; get a copy of ctrPt.v and put in d3
 sub.w  d1,d3  ; ctrPt.v - width/2 = ‘top’ in d3
 swap d3; move ‘top’ to hiword
 ; compute ‘left’
 move.w d4,d3  ; get a copy of ctrPt.h and put in loword d3
 sub.w  d2,d3  ; ctrPt.h - height/2 = ‘left’ in loword d3
;
; we now have ‘top’ in hiword & ‘left’ in loword d3
;
; compute ‘bottom’
 move.w d5,d6  ; get a copy of ctrPt.v in of d3
 add.w  d1,d6  ; ctrPt.v + width/2 = ‘bottom’ in d3
 swap d6; move ‘bottom’ to hiword  
 ; compute ‘right’
 move.w d4,d6  ; get a copy of ctrPt.h and put in loword d6
 add.w  d2,d6  ; ctrPt.h + height/2 = ‘right’ in loword d6
;
; we now have ‘bottom’ in hiword & ‘right’ in loword d6
;
; assign data to dest bitMap structure directly
;
 move.w d7,rowBytes(a2) ; put rowBytes for dest bitMap
 move.l d3,bounds+topLeft(a2) ; put coord pair in direct
 move.l d6,bounds+botRight(a2); put coord pair in direct
 move.w d7,d0    ; put rowbytes in d0
 mulu rowBytes(a1),d0; multiply rowBytes src*dest = d0
 lsl    #3,d0    ; multiply d0 by 8 for size of dest bitMap
 _NewPtrclear  ; allocate size of dest bitMap (a2) 
 move.l a0,baseaddr(a2)   
; move contents of a0 to baseAddr field
; of dest bitMap structure
 
;----------------------------------------------------------
;a0 = baseAddr for our locals 
 | d0 = rowbytes of srcMap
;a1 = points to bits of src bitMap 
 | d1 = width/2  (source)
;a2 = ptr to dest bitMap  | d2 = height/2    (source)
;a3 = dest bits  | d3 = current word srcMap
;a4 =   | d4 = copy of width of srcMap
;a5 = not used   | d5 = current destMap word
;a6 = not used   | d6 = insideLoop count (current bit)
;a7 = not used   | d7 = rowBytes of dest bitMap
;----------------------------------------------------------


; **********************************************
; **
; *Start actual rotation of bitMaps*
; **
; **********************************************
; Assumptions:
; height of destination bitMap = rowBytes of srcMap
;
; 
; Get the baseAddr for our locals into a0 - this will 
; make our program much more readable as we will let 
; the assembler do all the addr location calcs for us. 
; The addresses will be a fixed number while the 
; programs is (locked down) and running, therefore, 
; there is no runtime penalty for doing this addr 
; calculation
;

 lea  localVars,a0
;
; initialize d0 to contain the pointer to the srcMap
;
 move.w rowbytes(a1),d0 ; get rowbytes of srcMap
;
; get regs pointing to bits
;
 move.l (a1),a1  ; a1 pts to BITS of src bitMap
 move.l (a2),a3  ; a3 pts to BITS of dest bitMap

computeInitLowerLeft
; 
; compute the lower left corner of the destMap - this maps to 
; the upper left corner of the srcMap and will be used as base 
; from which to offset into the dest map when we need to set 
; a bit
;
 clr.l  d1; clear d1 
 move.w d0,d1    ; load rowBytes of srcMap
 ; (rowBytes of srcMap = ht of destMap)
 lsl.w  #3,d1    ; multiply by 8, get ht of destMap
 mulu.w d7,d1    ; multiply by rowbytes(destMap) = #words
 sub.w  d7,d1    ; in destMap less one row
 add.l  a3,d1  ; add computed offset to baseAddr of destMap
 move.l d1,lowerLeft-localVars(a0) ; init lowerLeft
;
;----------------------------------------------------------
;a0 = baseAddr for our locals |  d0 = rowbytes of srcMap
;a1 = points to bits of src bitMap | d1 = SCRATCH
;a2 = SCRATCH    | d2 = colmCounter
;a3 = dest bits  | d3 = current srcMap word 
;a4 = SCRATCH    | d4 = colmBitLoop counter
;a5 = not used   | d5 = current word destMap
;a6 = not used   | d6 = insideLoop count
;a7 = not used   | d7 = rowBytes of dest bitMap
;----------------------------------------------------------
;
;_Debugger; 
; init wordCount to zero 
;
 move.w #0,wordCount-localVars(a0) 
; set wordCount to one before we start
;
; init colmLoop counter
;
 move.w d7,d2  ; get rowBytes(dest)
 lsr    #1,d2  ; divide by 2 - d2 now contains outer counter
 move.w d2,colmCount-localVars(a0) 
 ; put the max value away for reference
 moveq.l#0,d2    ; set d2 to zero, use it for counter

colmLoop
;
; this is the outside loop
;
 move.w #15,d4   ; init colmBitLoop counter to 15
 move.l lowerLeft-localVars(a0),currentWord-localVars(a0)      
 ; init currentWord

colmBitLoop
;
; this is the loop that does the bit counting in the destMap - 
; it is really a misuse of the ‘dbra’ instruction since it is 
; not an independent loop but it is a quite cheap method of 
; doing our counting
;

rowLoop
;
; loop across each row in the srcMap and keep count each time 
; we do a row this ‘rowCount’ will used to compute the offset 
; from ‘lowerLeft’
;
 move.w (a1),d3  ; get a word to rotate into d3
 addq.w #2,wordCount-localVars(a0) 
 ; add 1 for each word •••jdo•••
 moveq.l#0,d6  ; clear d6 
 move.w #15,d6   ; initialize inside (bit) loop counter
 
innerLoop
;

; we will test each bit (15->0 ie. lt to rt) if it is set then 
; we worry about going out and setting it in destMap otherwise 
; we just increment the counters and test the next bit 
; (cause we ; cleared them all at init)
;
 btst d6,d3 ; test bit pointed to in the current src word
 ; by the inner (bit) loop counter
 beq    NoBitToChange
 ; if bits not set skip to next, we cleared
 ; them at init of bitMap else set 
 ; the corresponding bit before we bump the
 ; counter to the next bit
;
; bit needs to be set - get the word from memory, set bit and 
; put it back this is very time consuming as each word is moved 
; in/out of memory 16 times and a good place to look at for 
; optimization - it probably means a complete
; rethink of the way we are doing this now
;
 nop
 movea.lcurrentWord-localVars(a0),a2 
 ; get addr of currentWord
 move.w (a2),d5  ; get currentWord
 bset d4,d5 ; set proper bit in currentWord
 move.w d5,(a2)  ; put the currentWord word back
 ; into destMap (memory pt’d to by a3) 
 nop
 
noBitToChange
;
; bit was cleared at init of bitMap so we come here to 
; increment the counters - we need to move over 1 bit in the 
; srcWord and up 1 row in our destMap (which is always 
; subtracting rowBytes 
;
 sub.l  d7,currentWord-localVars(a0) 
 ; subtract rowBytes(destMap) from current word

 dbra d6,innerLoop ; keep looping (thru bits) till d6 = -1
 addq.l #2,a1  
 ; point to next word in srcMap - will take
 ; us all the way thru the srcMap as simple as that !
 ; Its the destMap that requires all the dang loops
 ; to get the addr of the corresponding word in the
 ; destMap

doneWithSrcWord
;
; at this point we’ve just completed a word from the srcMap
;
 cmp    wordCount-localVars(a0),d0 
; cmp wordCount to rowBytes-are we done with a row ?
 bne    rowLoop  ; no - do another word
 
doneWithRow
;
; we just completed a row so add 2 to the count
;
 move.w #0,wordCount-localVars(a0) 
; •••jdo••• reset wordCount to zero

;
; following use of dbra keeps track of our destMap bit for us
;
 move.l lowerLeft-localVars(a0),currentWord-localVars(a0)      
 ; re-init currentWord  
 ; set the currentWord to the lowerLeft(current)
 ; lowerLeft is moved over (right) by a word each
 ; time we do a row in the srcMap
 dbra d4,colmBitLoop ; go thru the bits (15->0) in the
 ; destMap - then exit and do it
 ; again for rowBytes/2 times

doneWithDestWord
;
; were done counting thru the bits of a destMap word - 
; at this point we have
; actually finished a column in the destMap. The column 
; is a multiple of
; 16 bits wide (and being at least 1 colm) and as tall 
; as the destMap is
; (which is also equal to rowBytes of the srcMap)
;
 addq.l #2,lowerLeft-localVars(a0) 
 ; this moves us into the next
 ; ‘column’ of bits in the destMap
 addq.w #1,d2    ; add 1 to the colmLoop counter
 cmp    colmCount-localVars(a0),d2 ; are we done yet ?
 bne    colmLoop ; no - do it again
 ; else we’re done
;
; were done so lets do our clean-up/restore 
; and go away gracefully
;
cleanUpTime ; clean-up and get outta dodge
 movem.l(sp)+,d3-d7/a2-a5 ; restore registers
 unlk a6; clean up stack frame
 move.l (sp)+,a0 ; return address
 add.l  #8,sp    ; pop parameters off stack
 jmp    (a0)     ; bye y’all!
 
;
; set up our data storage area
;
localVars ; the beginning of our local var storage area
colmCount ds.w 1
currentWord ds.l 1
lowerLeft ds.l 1
wordCount ds.w 1

 ENDP
 
 END

Listing 3 rot4Edit

PROGRAM rot4Edit;

{
••••••••••••••••••••••••••••••••••
 rot4Edit by John D. Olsen
idea by Scott Boyd, Greg Marriott
   and John Olsen
 for
 MacTutor Magazine
 © Jan 1988
••••••••••••••••••••••••••••••••••
}

uses
 {$LOAD pinterfaces.dump} 
 MemTypes,QuickDraw,OsIntf,PasLibIntf,
 ToolIntf,PackIntf,IntEnv,CursorCtl;

type
 integerPtr = ^INTEGER;
var
 item: Handle;
 itemHit, invertItem : INTEGER;
 map1, map2, map3, map4 : bitMap;
 rect1, rect2, rect3, rect4 : rect;
 
 
FUNCTION NewPtrClear( theSize: size ) : Ptr; EXTERNAL;
 
PROCEDURE Rotate( srcMap, destMap : BitMap ); EXTERNAL;
 
PROCEDURE NewBitMapClear( VAR theBitMap : BitMap );
 BEGIN
 WITH theBitMap, bounds DO
 BEGIN
 rowBytes := ((right - left + 15) DIV 16) * 2;
 baseAddr := NewPtrClear(rowBytes * (bottom - top));
 IF MemError <> noErr then baseAddr := NIL;
 END;
 END;
 
FUNCTION filterProc(theDialog: DialogPtr; VAR theEvent: EventRecord; 
VAR itemHit: INTEGER): BOOLEAN;
 CONST
 EnterKey = $03;
 BackspaceKey = $08;
 TabKey = $09;
 ReturnKey = $0D;
 ClearKey = $1B;
 DeleteKey = $7F;

 {bits of the event modifiers long word}
 CommandBit = 8;

 VAR
 itemNum: INTEGER;
 kind: INTEGER;
 box: Rect;
 charCode: INTEGER;
 theChar: Char;
 dialog: DialogPtr;
VAR
 noteDialog : DialogPtr;
 itemType : integer;

 BEGIN
 filterProc := FALSE;

 CASE theEvent.what OF
 nullEvent:
 BEGIN  { 2, 3, 4, 5 }
 GetDItem(theDialog, 2, kind, item, box);
 GetDItem(theDialog, 3, kind, item, rect2);
 GetDItem(theDialog, 4, kind, item, rect3);
 GetDItem(theDialog, 5, kind, item, rect4);
 CopyBits( thePort^.portBits, map1, box, map1.bounds, srcCopy, nil);
 Rotate( map1, map2 );
 Rotate( map2, map3 );    
 Rotate( map3, map4 );    
 CopyBits( map2, thePort^.portBits, map2.bounds, rect2, srcCopy, nil);
 CopyBits( map3, thePort^.portBits, map3.bounds, rect3, srcCopy, nil);
 CopyBits( map4, thePort^.portBits, map4.bounds, rect4, srcCopy, nil);
 DisposPtr( map2.baseAddr );
 DisposPtr( map3.baseAddr );
 DisposPtr( map4.baseAddr );
 END;
 keyDown, autoKey:
 BEGIN
 charCode := BAND(theEvent.message, charCodeMask);
 IF charCode IN [EnterKey] THEN 
 BEGIN  
 itemHit := 1;
 filterProc := true;
 EXIT(filterProc);
 END;
 IF charCode IN [ClearKey] THEN
 BEGIN
 IF theEvent.what = keyDown THEN DlgDelete(theDialog);
 theEvent.what := nullEvent;
 EXIT(filterProc)
 END;
 IF BTST(theEvent.modifiers, CommandBit) THEN
 BEGIN
 theChar := CHR(charCode);
 CASE theChar OF
 ‘X’,’x’: IF theEvent.what = keyDown THEN DlgCut(theDialog);
 ‘C’,’c’: IF theEvent.what = keyDown THEN DlgCopy(theDialog);
 ‘V’,’v’: IF theEvent.what = keyDown THEN DlgPaste(theDialog);
 OTHERWISE ;
 END;
 theEvent.what := nullEvent;
 EXIT(filterProc);
 END;
 END;
 END;
 END;

PROCEDURE EditThatSucker;
type
 strHandle = ^strPtr;
 strPtr = ^Str255;
var
 noteDialog : DialogPtr;
 itemType : integer;
 item : handle;
 box : rect;
BEGIN
 {Bring up the window}
 noteDialog := GetNewDialog( 1000, NIL, windowPtr(-1) );
 showWindow(noteDialog);
 SetPort(noteDialog);

 getDItem(noteDialog, 2, itemType, item, box);
 map1.bounds := box;
 NewBitMapClear( map1 );
 
 
 InitCursor;
 repeat
 ModalDialog(@filterProc, itemHit);
 until itemHit = 1;
 
 DisposDialog(noteDialog);
END;{EditThatSucker}

BEGIN {main}
 InitGraf(@thePort);
 BEGIN
 InitFonts;
 InitWindows;
 InitMenus;
 TEInit;
 InitDialogs(nil);
 END;

 invertItem := 1;
 EditThatSucker;
 
END.

Listing 4 Resource File for rot4Edit

#include “Types.r”

type ‘xRot’ as ‘STR ‘;

resource ‘xRot’ (0) {
 “Sample Rotation Application - Version 1.0 by John D. Olsen,RPS of Double 
Centre Surveying”
};


resource ‘DITL’ (32723) {
 { /* array DITLarray: 5 elements */
 /* [1] */
 {140, 326, 160, 354},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {3, 16, 147, 160},
 EditText {
 enabled,
 “What do you think about this  certainly covers”
 “all the angles of rotated text don’t you th”
 “ink ?”
 },
 /* [3] */
 {3, 165, 147, 309},
 EditText {
 enabled,
 “”
 },
 /* [4] */
 {152, 165, 296, 309},
 EditText {
 enabled,
 “”
 },
 /* [5] */
 {152, 16, 296, 160},
 EditText {
 enabled,
 “”
 }
 }
};

resource ‘DLOG’ (1000, “Rotated Text Dialog”) {
 {41, 16, 341, 372},
 documentProc,
 visible,
 noGoAway,
 0x0,
 32723,
 “Rotated TextEdit”
};
resource ‘BNDL’ (128) {
 ‘xRot’,
 0,
 { /* array TypeArray: 2 elements */
 /* [1] */
 ‘FREF’,
 { /* array IDArray: 1 elements */
 /* [1] */
 0, 128
 },
 /* [2] */
 ‘ICN#’,
 { /* array IDArray: 1 elements */
 /* [1] */
 0, 128
 }
 }
};

resource ‘FREF’ (128) {
 ‘APPL’,
 0,
 “”
};

resource ‘ICN#’ (128) {
 { /* array: 2 elements */
 /* [1] */
 $”FFFF FFFF 8000 8001 A400 807D AA00 8001"
 $”AE00 8039 AA00 8015 A000 8039 8000 8001"
 $”8000 8001 8000 8001 8000 8001 8000 8001"
 $”8000 8001 8000 8001 8000 8001 8000 8001"
 $”FFFF FFFF 8000 8001 8000 8001 8000 8001"
 $”8000 8001 8000 8001 8000 8001 8000 8001"
 $”8000 8001 9C00 8005 A800 8055 9C00 8075"
 $”8000 8055 BE00 8025 8000 8001 FFFF FFFF”,
 /* [2] */
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 }
};

John Olson wins our program of the month award for this interesting and original approach to an important problem in displaying text.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Whitethorn Games combines two completely...
If you have ever gone fishing then you know that it is a lesson in patience, sitting around waiting for a bite that may never come. Well, that's because you have been doing it wrong, since as Whitehorn Games now demonstrates in new release Skate... | Read more »
Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »

Price Scanner via MacPrices.net

B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for up to $150 off Apple’s new MSRP, starting at only $849. Free 1-2 day delivery is available to most US... Read more
M2 Mac minis on sale for $100-$200 off MSRP,...
B&H Photo has Apple’s M2-powered Mac minis back in stock and on sale today for $100-$200 off MSRP. Free 1-2 day shipping is available for most US addresses: – Mac mini M2/256GB SSD: $499, save $... Read more
Mac Studios with M2 Max and M2 Ultra CPUs on...
B&H Photo has standard-configuration Mac Studios with Apple’s M2 Max & Ultra CPUs in stock today and on Easter sale for $200 off MSRP. Their prices are the lowest available for these models... Read more
Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more

Jobs Board

Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Business Analyst | *Apple* Pay - Banco Popu...
Business Analyst | Apple PayApply now " Apply now + Apply Now + Start applying with LinkedIn Start + Please wait Date:Mar 19, 2024 Location: San Juan-Cupey, PR Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.