home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-10-27 | 11.4 KB | 324 lines | [TEXT/PJMM] |
- unit NeoTextBox;
-
- { Written in C by Bryan K. Ressler (Beaker), from Develop issue 9 }
-
- interface
-
- const
- ntbJustFull = 128;
-
- procedure NeoTextBox (theText: Ptr; textLen: longInt; wrapBox: Rect; align: integer; lhCode: integer; var endY: integer; var lhUsed: integer; var lineCount: integer);
-
- implementation
-
- uses
- Script, FixMath, QLowLevel, MyMathUtils;
-
- const
- kReturnChar = 13;
-
- {}
- {* NTBLineHeight - figures line height}
- {*}
- {* Input: theText the entire text that was given to the NeoTextBox call}
- {* textLen the length in bytes of the text}
- {* lhCode the line height code that was passed to NeoTextBox}
- {* startY VAR - we return the starting vertical pen location here}
- {*}
- {* Output: returns the line height to use}
- function NTBLineHeight (theText: Ptr; textLen: longInt; wrapBox: rect; lhCode: integer; var startY: integer): integer;
- const
- kTrueTypeTrap = $54;
- kUnimplTrap = $9f;
-
- var
- asc, desc: integer; { Used in the OutlineMetrics calls }
- fInfo: FontInfo; { The old-style font information record }
- frac: Point; { The fraction for the TrueType calls }
- lineHeight: integer; { The return value }
- hasTrueType: boolean;
- err: OSErr;
- begin
-
- GetFontInfo(fInfo);
- if lhCode < 0 then begin
-
- { If the user has specified variable-height lines, we need to try}
- { to determine the tallest ascent in the given text. We can only}
- { really do this if the font is a TrueType font. Otherwise, we}
- { punt and use old-fashioned GetFontInfo numbers.}
-
- frac.h := 1;
- frac.v := 1;
- hasTrueType := NGetTrapAddress(kTrueTypeTrap, ToolTrap) <> NGetTrapAddress(kUnimplTrap, ToolTrap);
- if hasTrueType & IsOutline(frac, frac) then begin
-
- { At this point we know the current font is a TrueType font, so}
- { we do an OutlineMetrics call with our full text. It will put}
- { the tallest character ascent into asc, and the deepest descent}
- { into desc. Then we choose between whichever's most between}
- { the old-style ascent/descent and the numbers we get from}
- { the OutlineMetrics call.}
- { }
-
- err := OutlineMetrics(textLen, theText, frac, frac, asc, desc, nil, nil, nil);
-
- end
- else begin
-
- { At this point we know the current font isn't TrueType, so we}
- { just use the old way of calculating line height.}
- err := -1;
- end;
- if err <> noErr then begin
- asc := 0;
- desc := 0;
- end;
- lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
- startY := wrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
- end
- else if lhCode = 0 then begin
-
- {}
- { If the user has specified "default" line height, he just wants us}
- { to get the line height from the FontInfo record.}
- { }
-
- lineHeight := fInfo.ascent + fInfo.descent + fInfo.leading;
- startY := wrapBox.top + fInfo.ascent + fInfo.leading;
-
- end
- else begin
-
- { If the user has provided a specific line height, we just trust}
- { them. We can't really generate too good of a starting vertical}
- { coordinate, but we munge one together anyway.}
- { }
-
- lineHeight := lhCode;
- startY := wrapBox.top + lhCode + fInfo.leading;
-
- end;
-
- NTBLineHeight := lineHeight;
- end;
-
- {}
- {* NTBDraw - draws a line with appropriate justification}
- {*}
- {* Input: breakCode the break code that was returned from StyledLineBreak}
- {* lineStart pointer to the beginning of the text for the current line}
- {* lineBytes the length in bytes of the the text for this line}
- {* wrapBox the box within which we're wrapping}
- {* align the text alignment as specified by the user}
- {* curY our current vertical pen coordinate}
- {* boxWidth the width of wrapBox (since NeoTextBox already calculated it)}
- {*}
- {* Output: none (draws on the screen)}
- {}
- procedure NTBDraw (breakCode: StyledLineBreakCode; lineStart: ptr; lineBytes: longInt; wrapBox: Rect; align: integer; curY: integer; boxWidth: integer);
- var
- blackLen: longInt; { Length of non-white characters }
- slop: integer; { Number of pixels of slop for full just }
- begin
-
- {}
- { The first thing we do here is determine the length of the "black" part}
- { of the current line. This excludes spaces, carriage returns, and other}
- { white stuff depending on the language. How do we know what to elim-}
- { inate? We DON'T! So we ask our friend the Script Manager to do it}
- { for us. VisibleLength returns the number of bytes that we should use}
- { for pixel width calculations.}
- { }
-
- blackLen := VisibleLength(lineStart, lineBytes);
-
- if align = ntbJustFull then begin
-
- {}
- { For full justification, we need to calculate the "slop" space on}
- { the line that's not filled up by the text. Then we move to the}
- { margin and get ready to draw BUT WAIT! If this is the last line of}
- { a paragraph, we need to draw it with whatever the system justifi-}
- { cation is. So if the break code indicates we're at the end of the}
- { text, or we can find a carriage return there ourselves, we just}
- { change the text alignment to the current default system text align-}
- { ment and fall through without drawing. If it's just another line}
- { within a paragraph, we use the handy Script Manager routine DrawJust}
- { to draw it justified. In languages like Arabic, full justification}
- { is performed differently than just spacing out the words. DrawJust}
- { handles cases like these correctly.}
- { }
- { Note that when we go looking for the carriage return at the end of}
- { the line, it's okay to simply index to the last byte of the string.}
- { This is because the carriage return character, 0x0d, is in the}
- { control-code range, which is defined to never be the low byte of a}
- { two byte character.}
- { }
-
- slop := boxWidth - TextWidth(lineStart, 0, blackLen);
- MoveTo(wrapBox.left, curY);
- if (breakCode = smBreakOverflow) | (AddPtrLong(lineStart, lineBytes - 1)^ = kReturnChar) then begin
- align := GetSysJust;
- end
- else begin
- DrawJust(lineStart, blackLen, slop);
- end;
- end;
-
- {}
- { For the rest of the text alignments (left, center, and right), we just}
- { move the pen to the right place and draw the text with DrawText. Note}
- { that the alignments that could have come into the NeoTextBox call have}
- { been resoved down to one of the four constants teForceLeft, teJustRight}
- { teJustCenter, or ntbJustFull.}
- { }
-
- case align of
- teForceLeft, teJustLeft:
- MoveTo(wrapBox.left, curY);
- teJustRight:
- MoveTo(wrapBox.right - TextWidth(lineStart, 0, blackLen), curY);
- teJustCenter:
- MoveTo(wrapBox.left + (boxWidth - TextWidth(lineStart, 0, blackLen)) div 2, curY);
- otherwise
- ;
- end;
- if align <> ntbJustFull then begin
- DrawText(lineStart, 0, lineBytes);
- end;
- end;
-
- {}
- {* NeoTextBox - word-wraps text inside a given box}
- {*}
- {* Input: theText the text we need to wrap}
- {* textLen the length in bytes of the text}
- {* wrapBox the box within which we're wrapping}
- {* align the text alignment}
- {* teForceLeft, teFlushLeft left justified}
- {* teJustCenter, teCenter center justified}
- {* teJustRight, teFlushRight right justified}
- {* ntbJustFull full justified}
- {* teJustLeft, teFlushDefault system justified}
- {* lhCode the line height code that was passed to NeoTextBox}
- {* < 0 variable - based on tallest character}
- {* 0 default - based on GetFontInfo}
- {* > 0 fixed - use lhCode as the line height}
- {* endY VAR - if non-nil, the vertical coord of the last line}
- {* lhUsed VAR - if non-nil, the line height used to draw the text}
- {*}
- {* Output: returns the number of line drawn total (even if they drew outside of}
- {* the boundries of wrapBox)}
- {}
- procedure NeoTextBox (theText: Ptr; textLen: longInt; wrapBox: Rect; align: integer; lhCode: integer; var endY: integer; var lhUsed: integer; var lineCount: integer);
- var
- oldClip: RgnHandle; { Saved clipping region }
- breakCode: StyledLineBreakCode; { Returned code from StyledLineBreak }
- fixedMax: Fixed; { boxWidth converted to fixed point }
- wrapWid: Fixed; { Width to wrap to }
- boxWidth: integer; { Width of the wrapBox }
- lineBytes: longInt; { Number of bytes in one line }
- lineHeight: integer; { Calculated line height }
- curY: integer; { Current vertical pen location }
- textLeft: longInt; { Pointer to remaining bytes of text }
- lineStart: Ptr; { Pointer to beginning of a line }
- textEnd: Ptr; { Pointer to the end of input text }
- begin
- {}
- { First, we save the old clipping region and clip to wrapBox. Then, figure}
- { the width of wrapBox, and make a fixed point version of it. Also, resolve}
- { the text alignment teFlushDefault to be the default system text alignment.}
- { }
- oldClip := NewRgn;
- GetClip(oldClip);
- ClipRect(wrapBox);
- boxWidth := wrapBox.right - wrapBox.left;
- fixedMax := Long2Fix(boxWidth);
- if align = teFlushDefault then begin
- align := GetSysJust;
- end;
-
- {}
- { Now we call NTBLineHeight to calculate the appropriate line height. It}
- { also figures our starting vertical pen location in curY based on the}
- { line height and other metric parameters. Clear lineCount, set}
- { lineStart to point to the beginning of the text, calculate textEnd for}
- { comparison to know when we're done, and preset textLeft to be all the}
- { text.}
- { }
-
- lineHeight := NTBLineHeight(theText, textLen, wrapBox, lhCode, curY);
- lineCount := 0;
- lineStart := theText;
- textEnd := AddPtrLong(theText, textLen);
- textLeft := textLen;
-
- {}
- { This is the main wrap-and-draw loop. I bet you never thought wrapping}
- { text could be so easy...}
- { }
-
- while textLeft > 0 do begin
-
- {}
- { Every line, we have to preset lineBytes to something non-zero.}
- { This tells StyledLineBreak that we're drawing the first format}
- { run on the line (of course, for us, there's only ONE format run}
- { total). Also preset wrapWid. StyledLineBreak will always modify}
- { lineBytes (to tell you how many bytes are on this line), and will}
- { modify wrapWid, so we have to reset them each line.}
- { }
-
- lineBytes := 1;
- wrapWid := fixedMax;
-
- breakCode := StyledLineBreak(lineStart, textLeft, 0, textLeft, 0, wrapWid, lineBytes);
-
- {}
- { Now that the Script Manager has done all the really hard work for}
- { us, we draw the line. We already knew lineStart, StyledLineBreak}
- { gave us lineBytes, which we pass to NTBDraw. It'll handle the}
- { different text alignments itself.}
- { }
-
- NTBDraw(breakCode, lineStart, lineBytes, wrapBox, align, curY, boxWidth);
-
- {}
- { Now we advance our vertical position down by the height of one}
- { line, advance lineStart by the number of bytes we just drew,}
- { calculate a new textLeft, and increment our line count.}
- { }
-
- curY := curY + lineHeight;
- lineStart := AddPtrLong(lineStart, lineBytes);
- textLeft := textLeft - lineBytes;
- lineCount := lineCount + 1;
-
- end;
-
- {}
- { Well that was a job well done. Let's return some useful values, too.}
- { If the user gave pointers for endY and lhUsed, we stuff our ending}
- { vertical coordinate and the line height we calculated into those,}
- { respectively. These allow the guy to put something after the wrapped}
- { text (or at least know the right place to put it).}
- { }
-
- endY := curY - lineHeight;
- lhUsed := lineHeight;
-
- {}
- { Finally, restore the clipping region, dispose of the region, and}
- { return the TOTAL number of lines drawn (note that we didn't stop}
- { drawing when curY advanced past wrapBox->bottom. This way, the user}
- { could tell that the text overflowed wrapBox. If you want to know how}
- { many lines fit, divide wrapBox by lhUsed. This way, you get the best}
- { of both worlds.}
- { }
-
- SetClip(oldClip);
- DisposeRgn(oldClip);
- end;
-
- end.