Text Wrapper.ahk
; AutoHotkey Version: 1.0.29 ; Language: English ; Platform: Windows XP ; Author: Gehn WillowGlade <[email protected]> ; ; Script Function: ; Wraps text manually by adding in newline statements to a string using ; a configurable methodology. Configuration is done by setting specific ; variables, detailed below. Settings are remembered between calls. Most ; variables have a default and hence do not absolutely require setting. ; Text is wrapped by breaking the lines as long as possible, at certain ; 'break characters' defined in sets. These sets have priority, starting ; at 1. Wrap Text will attempt to break the line using set 1, and if it ; fails, it will continue on to set 2, and so forth, until it runs out of ; sets. The result is that it will prefer wrapping very short lines to ; wrapping at lower priority break characters. ; ; Variables: ; textToWrap - The text to be wrapped. No default. ; maxCharactersPerLine - The number of characters that are allowed ; on a line before a wrap is attempted. This ; setting defaults to 80. ; breakType - This can be set to either 'after', 'before' ; or 'eat'. 'after' will have lines break after ; the break character. 'before' will have the ; lines break before the break character. 'eat' ; will destroy the break character entirely. ; This setting defaults to 'after'. ; breakCharacterSet[*] - A set of individual characters which all ; have the same priority, starting at 1. Do ; not include spaces unless you want space to ; be a break character. Use a blank set to end ; the set list. ; breakCharacterSet[*]_type - This allows the break type to be set on a per ; break character set basis. This setting ; defaults to the breakType setting. ; forceWrap - This will force wrapping of the text, even if ; no break characters are found. In such a case ; the line will break right at the limit. Set ; this option using true or false. The default ; is false. ; wrappedText - This variable is not set. Instead, this is the ; text, wrapped, returned by Wrap Text. ; ; Example: ; textToWrap := "C:\Games\Guild Wars\GW.exe" ; maxCharactersPerLine := 10 ; breakCharacterSet[1] := "\" ; breakCharacterSet[2] := " " ; breakCharacterSet[3] := "" ; Gosub , WrapText ; ; wrappedText would be: "C:\Games\`nGuild`nWars\`nGW.exe" ; Invoked externally to perform core function WrapText: { Gosub , WrapText_Initialize If ErrorLevel <> 0 Return Gosub , WrapText_BreakIntoLines Gosub , WrapText_CombineLines Return } ; Makes sure all variables are user set or filled with defaults (where applicable) WrapText_Initialize: { If ( textToWrap = "" ) { MsgBox , 48 , WrapText Error , The variable 'textToWrap' must be set when WrapText is called. ErrorLevel := 1 Return } If ( maxCharactersPerLine = "" ) { maxCharactersPerLine := 80 } Else If maxCharactersPerLine is not integer { MsgBox , 48 , WrapText Error , The variable 'maxCharactersPerLine' must be a positive, nonzero integer. ErrorLevel := 1 Return } Else If ( maxCharactersPerLine <= 0 ) { MsgBox , 48 , WrapText Error , The variable 'maxCharactersPerLine' must be a positive, nonzero integer. ErrorLevel := 1 Return } If ( breakType = "" ) { breakType := "after" } Else If ( !( breakType = "after" OR breakType = "before" OR breakType = "eat" ) ) { MsgBox , 48 , WrapText Error , The variable 'breakType' may only be set to 'after', 'before' or 'eat'. ErrorLevel := 1 Return } If ( breakCharacterSet[1] = "" ) { breakCharacterSet[1] := " " } Loop { If ( breakCharacterSet[%a_index%] == "" ) Break If ( !( breakCharacterSet[%a_index%]_breakType = "" OR breakCharacterSet[%a_index%]_breakType = "after" OR breakCharacterSet[%a_index%]_breakType = "before" OR breakCharacterSet[%a_index%]_breakType = "eat" ) ) { MsgBox , 48 , WrapText Error , The variable 'breakCharacterSet[%a_index%]_breakType' may only be set to 'after', 'before' or 'eat'. ErrorLevel := 1 Return } } If ( forceWrap = "" ) { forceWrap := false } Else If ( !( forceWrap = true OR forceWrap = false ) ) { MsgBox , 48 , WrapText Error , The variable 'forceWrap' must be either true or false. ErrorLevel := 1 Return } Return } ; Breaks the text into lines, as needed WrapText_BreakIntoLines: { ; Chop off the ends of lines, to create new lines, until all lines are under the character limit WrapText_line[1] := textToWrap WrapText_lineCount := 1 Loop { WrapText_currentLineNumber := a_index StringLen , WrapText_currentLineLength , WrapText_line[%WrapText_currentLineNumber%] If ( WrapText_currentLineLength <= maxCharactersPerLine ) Break Else { ; Loop through break character sets until a break character is found, or we run out of sets WrapText_breakCharacterPosition := -1 Loop { ; Search backwards from farthest possible character to find a break character WrapText_currentBreakCharacterSet := a_index If ( ( WrapText_breakCharacterPosition >= 0 ) OR ( ( breakCharacterSet[%WrapText_currentBreakCharacterSet%] = "" ) AND !forceWrap ) ) Break If ( ( breakCharacterSet[%WrapText_currentBreakCharacterSet%] = "" ) AND forceWrap ) WrapText_forceWrapThisLine := true Else WrapText_forceWrapThisLine := false If ( breakCharacterSet[%WrapText_currentBreakCharacterSet%]_breakType != "" ) WrapText_currentBreakType := breakCharacterSet[%WrapText_currentBreakCharacterSet%]_breakType Else WrapText_currentBreakType := breakType If ( WrapText_currentBreakType = "after" ) WrapText_currentPosition := maxCharactersPerLine Else WrapText_currentPosition := maxCharactersPerLine + 1 Loop , %maxCharactersPerLine% { If ( WrapText_forceWrapThisLine ) WrapText_breakCharacterPosition := 1 Else { StringMid , WrapText_characterAtCurrentPosition , WrapText_line[%WrapText_currentLineNumber%] , WrapText_currentPosition , 1 StringGetPos , WrapText_breakCharacterPosition , breakCharacterSet[%WrapText_currentBreakCharacterSet%] , %WrapText_characterAtCurrentPosition% } If ( WrapText_breakCharacterPosition >= 0 ) { WrapText_lineCount += 1 If ( WrapText_currentBreakType = "after" ) { StringTrimLeft , WrapText_line[%WrapText_lineCount%] , WrapText_line[%WrapText_currentLineNumber%] , WrapText_currentPosition StringLeft , WrapText_line[%WrapText_currentLineNumber%] , WrapText_line[%WrapText_currentLineNumber%] , WrapText_currentPosition } Else If ( WrapText_currentBreakType = "before" ) { StringTrimLeft , WrapText_line[%WrapText_lineCount%] , WrapText_line[%WrapText_currentLineNumber%] , WrapText_currentPosition - 1 StringLeft , WrapText_line[%WrapText_currentLineNumber%] , WrapText_line[%WrapText_currentLineNumber%] , WrapText_currentPosition - 1 } Else ; WrapText_currentBreakType = "eat" { StringTrimLeft , WrapText_line[%WrapText_lineCount%] , WrapText_line[%WrapText_currentLineNumber%] , WrapText_currentPosition StringLeft , WrapText_line[%WrapText_currentLineNumber%] , WrapText_line[%WrapText_currentLineNumber%] , WrapText_currentPosition - 1 } Break } WrapText_currentPosition -= 1 } } } } Return } ; Recombines the text into a single variable WrapText_CombineLines: { wrappedText := "" Loop , %WrapText_lineCount% { WrapText_currentLine := WrapText_line[%a_index%] wrappedText := wrappedText "`n" WrapText_currentLine } StringTrimLeft , wrappedText , wrappedText , 1 Return }
I'm mostly happy with the result. It seems fully functional - if anybody spots a bug please let me know. Any other comments would be nice too - this is the first time I've actually put out any code for public display and I imagine I could use some feedback.
However, there's three layers of looping in the WrapText_BreakIntoLines function however, and combined with the ifs in said section, there's a bit more nesting than I'd like. It makes the code less clear than it could be. I considered, and am still considering, moving some of the code from WrapText_BreakIntoLines into other functions. That could make the function much clearer in some ways, but then you'd have even more variables crossing functions and more function call overhead. Thoughts on this issue would be appreciated.
Edit: I apologize that some of the lines are really long, it makes the formatting here (and in any text editor you may paste it into) rather ugly.