Code: Select all
; "DrawTextAdvanced" by c4s (v1.13, 06.02.2011)
;
;
;- Description
; It creates a transparent image with the specified text. Optionally the text can be
; wrapped if a defined width has been reached or #LF$ was found. The whole procedure
; will be configurated via the following structure. All settings, except the text of
; course, are optional and the returning value will be the newly created image, see:
;
;
;- Options
; "Text.s"
; The text that should be displayed. Use #LF$ as a line-break. Must be set!
; "TextColor.l"
; RGBA color of the text.
; Default: $FF000000 (fully visible black)
; "TextFontID.i"
; FontID for the text.
; Default: #PB_Default
;
; "Position.l"
; Position of the text: Left, Right, Center.
; Default: #DTA_PositionLeft
; "PositionWidth.l"
; Define the width of the wrapping area. The text will wrap either if this width is
; reached or an #LF$ character was found. Set it to "0" to just watch for the #LF$.
; Note that it will be filled with the width of the final created image anyway.
; Default: 0
; "PositionHeight.l"
; This is ignored but will be filled with the height of the created image.
; Default: 0
; "PositionAngle.f"
; Rotate the text counter-clockwise in degrees. A value of "0.0" means no rotation.
; Default: 0.0
;
; "Wrap.l"
; #DTA_True to enable, #DTA_False to disable the text wrapping.
; Default: #DTA_True
; "WrapSpacing.l"
; Space between each text line in pixels. Can be positive or negative.
; Default: 0
;
; "Outline.l"
; #DTA_True to enable, #DTA_False to disable the outline around the text.
; Default: #DTA_False
; "OutlineColor.l"
; RGBA color of the outline.
; Default: $FFFFFFFF (fully visible white)
; "OutlineSize.l"
; Size in pixels of the outline.
; Default: 1
;
; "ImageNr.l"
; Works as every (image) function of PureBasic. Either use a constant value or
; #PB_Any. The procedure will then return the new image number or true/false.
; Default: #PB_Any
; "ImageSimulate.l"
; When it's #DTA_True, no image will be created. This could be useful if just the
; returned width and height are needed.
; Default: #DTA_False
;
;
;- Notes
; ·Important: The options in the passed structure will be corrected and replaced if
; they are wrong, so reusing it might not work.
; ·Sometimes the text might be cropped because TextWidth() returns a wrong value. Is
; this a bug of PureBasic?
; ·Outline doesn't work if color of text is $00000000 or both colors are the same,
; however a primitive workaround is included.
; ·An ImageNr of 0 doesn't work because it will get the default #PB_Any.
EnableExplicit
#DTA_True = 1
#DTA_False = -1
#DTA_PositionLeft = 1
#DTA_PositionRight = 2
#DTA_PositionCenter = 3
Structure DrawTextAdvanced_Options
Text.s
TextColor.l
TextFontID.i
Position.l
PositionWidth.l
PositionHeight.l
PositionAngle.f
Wrap.l
WrapSpacing.l
Outline.l
OutlineColor.l
OutlineSize.l
ImageNr.l
ImageSimulate.l
EndStructure
Structure DrawTextAdvanced_Line
Text.s
X.l
Y.l
Width.l
Height.l
EndStructure
Macro Center(Value, Max)
(((Max) - (Value)) / 2)
EndMacro
Macro MidFast(String, StartPos, Length)
PeekS(@String + ((StartPos - 1) * SizeOf(Character)), Length)
EndMacro
Procedure DrawTextAdvanced(*Options.DrawTextAdvanced_Options)
Protected NewList Lines.DrawTextAdvanced_Line()
Protected WrapCPosition, WrapC.s, WrapWord.s
Protected WrapLineTemp1.s, WrapLineTemp1Width, WrapLineTemp2.s, WrapLineTemp2Width
Protected WrapLine.s, WrapLineWidth, WrapLineHeight
Protected LineWidthMax, LineHeightMax, LineYTemp
Protected RotateOldX, RotateOldY, RotateOldWidth, RotateOldHeight
Protected RotateSinR.f, RotateCosR.f, RotateX, RotateY
Protected OutlineX, OutlineY, OutlineRangeUp, OutlineRangeDown, OutlineRangeRight
Protected OutlineXScan, OutlineYScan, OutlineTrailingOffset, OutlineSkipped
Protected ImageNr, ImageNrTemp
;{ Check, correct and set default options
;{ Text
If Len(*Options\Text) = 0
ProcedureReturn #False
EndIf
If *Options\TextColor = 0
*Options\TextColor = $FF000000
EndIf
If *Options\TextFontID = 0
*Options\TextFontID = #PB_Default
EndIf
;}
;{ Position
If *Options\Position <> #DTA_PositionLeft And *Options\Position <> #DTA_PositionRight And *Options\Position <> #DTA_PositionCenter
*Options\Position = #DTA_PositionLeft
EndIf
If *Options\PositionWidth < 1
*Options\PositionWidth = 0
EndIf
If *Options\PositionHeight < 1
*Options\PositionHeight = 0
EndIf
If *Options\PositionAngle = 0
*Options\PositionAngle = 0.0
EndIf
;}
;{ Wrap
If *Options\Wrap <> #DTA_True And *Options\Wrap <> #DTA_False
*Options\Wrap = #DTA_True
EndIf
If *Options\WrapSpacing = 0
*Options\WrapSpacing = 0
EndIf
;}
;{ Outline
If *Options\Outline <> #DTA_True And *Options\Outline <> #DTA_False
*Options\Outline = #DTA_False
EndIf
If *Options\OutlineColor = 0
*Options\OutlineColor = $FFFFFFFF
EndIf
If *Options\OutlineSize < 1
*Options\OutlineSize = 1
EndIf
;}
;{ Image
If *Options\ImageNr = 0
*Options\ImageNr = #PB_Any
EndIf
If *Options\ImageSimulate <> #DTA_True And *Options\ImageSimulate <> #DTA_False
*Options\ImageSimulate = #DTA_False
EndIf
;}
; Workaround (Otherwise Outline wouldn't work):
If *Options\Outline = #DTA_True
If Alpha(*Options\TextColor) = $00
*Options\TextColor | $01000000
EndIf
If *Options\TextColor = *Options\OutlineColor
*Options\OutlineColor - 1
EndIf
EndIf
;}
;{ Analyze text: Lines, Dimension, Position
;{ Get every line and their width & height
ImageNrTemp = CreateImage(#PB_Any, 1, 1) ; Needed for TextWidth() & TextHeight()
If ImageNrTemp
If StartDrawing(ImageOutput(ImageNrTemp))
DrawingFont(*Options\TextFontID)
Repeat
;{ Get line
WrapLine = "" : WrapLineWidth = 0 : WrapLineHeight = 0
If *Options\Wrap = #DTA_True
;{ Wrap
Repeat
;{ Get word
WrapWord = "" : WrapC = ""
Repeat
WrapWord + WrapC
WrapCPosition + 1
WrapC = MidFast(*Options\Text, WrapCPosition, 1)
Until WrapC = " " Or WrapC = #LF$ Or WrapC = ""
;}
WrapLineTemp1 + WrapWord
WrapLineTemp1Width = TextWidth(WrapLineTemp1)
If *Options\PositionWidth <> 0 And WrapLineTemp1Width > *Options\PositionWidth And WrapLineTemp2 ; Break if width is defined and has been reached
WrapLine = WrapLineTemp2
WrapLineWidth = WrapLineTemp2Width
WrapLineHeight = TextHeight(WrapLineTemp2)
WrapLineTemp1 = WrapWord ; Add "wrapped" word to next temp line
WrapLineTemp2 = ""
WrapC = " " ; Don't go out of loop
WrapCPosition - 1 ; Do the previous step again
Break
ElseIf WrapC = #LF$ Or WrapC = "" ; Regular line break
WrapLine = WrapLineTemp1
WrapLineWidth = WrapLineTemp1Width
WrapLineHeight = TextHeight(WrapLineTemp1 + #LF$)
WrapLineTemp1 = "" ; Clear temp line
WrapLineTemp2 = ""
;WrapC = WrapC
;WrapCPosition = WrapCPosition
Break
Else
WrapLineTemp1 + " "
EndIf
WrapLineTemp2 = WrapLineTemp1
WrapLineTemp2Width = WrapLineTemp1Width
ForEver
;}
Else
;{ Normal
WrapLine = *Options\Text
WrapLineWidth = TextWidth(WrapLine)
WrapLineHeight = TextHeight(WrapLine)
;}
EndIf
;}
; Add line to list:
If AddElement(Lines())
With Lines()
\Text = WrapLine
\Width = WrapLineWidth
\Height = WrapLineHeight
EndWith
EndIf
Until WrapC = "" ; No characters left in "Get line" loop?
StopDrawing()
EndIf
FreeImage(ImageNrTemp)
EndIf
;}
;{ Get dimension of whole block (all lines)
ForEach Lines()
If Lines()\Width > LineWidthMax : LineWidthMax = Lines()\Width : EndIf
LineHeightMax + Lines()\Height + *Options\WrapSpacing
Next
*Options\PositionWidth = LineWidthMax
*Options\PositionHeight = LineHeightMax - *Options\WrapSpacing
If *Options\Outline = #DTA_True
*Options\PositionWidth + (*Options\OutlineSize * 2)
*Options\PositionHeight + (*Options\OutlineSize * 2)
EndIf
;}
;{ Set position of each line
ForEach Lines()
Select *Options\Position
Case #DTA_PositionLeft
Lines()\X = 0
If *Options\Outline = #DTA_True : Lines()\X + *Options\OutlineSize : EndIf
Case #DTA_PositionRight
Lines()\X = *Options\PositionWidth - Lines()\Width
If *Options\Outline = #DTA_True : Lines()\X - *Options\OutlineSize : EndIf
Case #DTA_PositionCenter
Lines()\X = Round(Center(Lines()\Width, *Options\PositionWidth), #PB_Round_Nearest)
EndSelect
Lines()\Y = LineYTemp
If *Options\Outline = #DTA_True : Lines()\Y + *Options\OutlineSize : EndIf
LineYTemp + Lines()\Height + *Options\WrapSpacing
Next
;}
If *Options\PositionAngle <> 0.0
;{ Apply rotation to block dimension and line position
; Based on code by STARGÅTE (http://www.purebasic.fr/german/viewtopic.php?p=285528#p285528)
RotateSinR = Sin(Radian(*Options\PositionAngle))
RotateCosR = Cos(Radian(*Options\PositionAngle))
; Correct dimension of block:
RotateOldWidth = *Options\PositionWidth
RotateOldHeight = *Options\PositionHeight
*Options\PositionWidth = Abs(RotateCosR * RotateOldWidth) + Abs(RotateSinR * RotateOldHeight)
*Options\PositionHeight = Abs(-RotateSinR * RotateOldWidth) + Abs(RotateCosR * RotateOldHeight)
; Correct position of each line:
If RotateCosR < 0
RotateX - (RotateCosR * RotateOldWidth)
EndIf
If RotateSinR < 0
RotateX - (RotateSinR * RotateOldHeight)
EndIf
If RotateSinR > 0
RotateY + (RotateSinR * RotateOldWidth)
EndIf
If RotateCosR < 0
RotateY - (RotateCosR * RotateOldHeight)
EndIf
ForEach Lines()
RotateOldX = Lines()\X
RotateOldY = Lines()\Y
Lines()\X = RotateX + ((RotateCosR * RotateOldX) + (RotateSinR * RotateOldY))
Lines()\Y = RotateY + ((-RotateSinR * RotateOldX) + (RotateCosR * RotateOldY))
Next
;}
EndIf
If *Options\PositionWidth < 1 Or *Options\PositionHeight < 1 Or ListSize(Lines()) = 0 ; Something went wrong
ProcedureReturn #False
EndIf
;}
If *Options\ImageSimulate = #DTA_True
ProcedureReturn #True
EndIf
;{ Create image
ImageNr = CreateImage(*Options\ImageNr, *Options\PositionWidth, *Options\PositionHeight, 32 | #PB_Image_Transparent)
If *Options\ImageNr <> #PB_Any : ImageNr = *Options\ImageNr : EndIf
If ImageNr
If StartDrawing(ImageOutput(ImageNr))
DrawingMode(#PB_2DDrawing_Transparent | #PB_2DDrawing_AlphaBlend)
;{ Draw text
DrawingFont(*Options\TextFontID)
ForEach Lines()
If *Options\PositionAngle = 0.0
DrawText(Lines()\X, Lines()\Y, Lines()\Text, *Options\TextColor)
Else
DrawRotatedText(Lines()\X, Lines()\Y, Lines()\Text, *Options\PositionAngle, *Options\TextColor)
EndIf
Next
;}
If *Options\Outline = #DTA_True
;{ Draw outline
; Based on code by Demivec (http://www.purebasic.fr/english/viewtopic.php?p=345086#p345086)
; (Also check out his much faster procedure: http://www.purebasic.fr/english/viewtopic.php?p=345320#p345320)
While OutlineY < *Options\PositionHeight
; Set ranges:
OutlineRangeUp = OutlineY - *Options\OutlineSize
If OutlineRangeUp < 0 : OutlineRangeUp = 0 : EndIf
OutlineRangeDown = OutlineY + *Options\OutlineSize
If OutlineRangeDown > *Options\PositionHeight - 1 : OutlineRangeDown = *Options\PositionHeight - 1 : EndIf
OutlineSkipped = 0
OutlineX = 0
Repeat
If Point(OutlineX, OutlineY) = *Options\TextColor
If OutlineSkipped > *Options\OutlineSize : OutlineSkipped = *Options\OutlineSize : EndIf
OutlineTrailingOffset = 0
Repeat
; Columns to be filled are always left of current column
For OutlineXScan = OutlineX - OutlineSkipped To OutlineX - OutlineTrailingOffset ; Include current column if not trailing border
; Plot scanline pixel if not in current column (x):
If OutlineXScan <> OutlineX
If Point(OutlineXScan, OutlineY) <> *Options\OutlineColor : Plot(OutlineXScan, OutlineY, *Options\OutlineColor) : EndIf
EndIf
; Check up:
For OutlineYScan = OutlineY - 1 To OutlineRangeUp Step -1
If Point(OutlineXScan, OutlineYScan) <> *Options\TextColor
If Point(OutlineXScan, OutlineYScan) <> *Options\OutlineColor : Plot(OutlineXScan, OutlineYScan, *Options\OutlineColor) : EndIf
EndIf
Next
; Check down:
For OutlineYScan = OutlineY + 1 To OutlineRangeDown
If Point(OutlineXScan, OutlineYScan) <> *Options\TextColor
If Point(OutlineXScan, OutlineYScan) <> *Options\OutlineColor : Plot(OutlineXScan, OutlineYScan, *Options\OutlineColor) : EndIf
EndIf
Next
Next
If OutlineTrailingOffset
OutlineX - 1 ; Adjust loop variable for next unscanned column
Break
EndIf
; Check next (right) columns:
OutlineRangeRight = OutlineX + *Options\OutlineSize
If OutlineRangeRight > *Options\PositionWidth - 1
OutlineRangeRight = *Options\PositionWidth - 1
EndIf
OutlineSkipped = 0
OutlineTrailingOffset = 1
For OutlineXScan = OutlineX + 1 To OutlineRangeRight
If Point(OutlineXScan, OutlineY) = *Options\TextColor ; Found a new current column
OutlineTrailingOffset = 0
Break
Else
OutlineSkipped + 1
EndIf
Next
If OutlineSkipped <> 0
OutlineX + OutlineSkipped + 1
EndIf
Until OutlineSkipped = 0
Else
OutlineSkipped + 1
EndIf
OutlineX + 1
Until OutlineX = *Options\PositionWidth
OutlineY + 1
Wend
;}
EndIf
StopDrawing()
EndIf
EndIf
;}
ProcedureReturn ImageNr
EndProcedure
;- Example
;{
Enumeration
#Window
#ImageGadget
#Image
#Option1
#Option2
#Option3
#Option4
#Font
#Timer
EndEnumeration
#Width = 400
#Height = 400
#Text = "Test Line 1" + #LF$ + "Test test Line 2" + #LF$ + "Test test test Line 3"
LoadFont(#Font, "Arial", 24, #PB_Font_Bold | #PB_Font_HighQuality | #PB_Font_Italic)
Procedure CreateExampleImage1()
Protected DTAO.DrawTextAdvanced_Options, DTAImageNr, X, Y
Static InitOptions
Static Color, ColorStep, Angle.f, Spacing, SpacingStep
; "Animate" some options:
If InitOptions = #False
InitOptions = #True
Color = $80 : ColorStep = 2
Angle = 0
Spacing = 0 : SpacingStep = 1
EndIf
Color + ColorStep
If Color >= $FD : ColorStep = -2 : ElseIf Color <= $02 : ColorStep = 2 : EndIf
Angle - 2
Spacing + SpacingStep
If Spacing >= 15 : SpacingStep = -1 : ElseIf Spacing <= -10 : SpacingStep = 1 : EndIf
; Set up the options:
With DTAO
\Text = #Text
\TextColor = RGBA(Color / 2, Color / 2, $FF, $FF - Color)
\TextFontID = FontID(#Font)
\Position = #DTA_PositionCenter
\PositionAngle = Angle
\WrapSpacing = Spacing
\Outline = #DTA_True
\OutlineColor = RGBA(Color, Color, Color, $AA)
\OutlineSize = 2
EndWith
; Create DTA image and draw it on #Image:
DTAImageNr = DrawTextAdvanced(@DTAO)
If DTAImageNr
CreateImage(#Image, #Width, #Height)
StartDrawing(ImageOutput(#Image))
; Draw background gradient:
DrawingMode(#PB_2DDrawing_Gradient)
CircularGradient(#Width / 2, #Height / 2, #Width / 2)
GradientColor(0.0, $F5CFBD)
GradientColor(1.0, $E9926B)
Box(0, 0, #Width, #Height)
; Draw centered DTA image:
X = Center(DTAO\PositionWidth, #Width) : Y = Center(DTAO\PositionHeight, #Height)
DrawAlphaImage(ImageID(DTAImageNr), X, Y)
StopDrawing()
FreeImage(DTAImageNr)
EndIf
EndProcedure
Procedure CreateExampleImage2()
Protected DTA1O.DrawTextAdvanced_Options, DTA1ImageNr, DTA2O.DrawTextAdvanced_Options, DTA2ImageNr, X, Y
Static InitOptions
Static Angle.f, AngleStep
; "Animate" some options:
If InitOptions = #False
InitOptions = #True
Angle = 0 : AngleStep = 0.5
EndIf
Angle + AngleStep
If Angle >= 10 : AngleStep = -0.5 : ElseIf Angle <= -10 : AngleStep = 0.5 : EndIf
; Set up the options:
With DTA1O
\Text = #Text
\TextColor = $FF202020
\TextFontID = FontID(#Font)
\Position = #DTA_PositionCenter
\PositionAngle = Angle
EndWith
With DTA2O
\Text = #Text
\TextColor = $60202020
\TextFontID = FontID(#Font)
\Position = #DTA_PositionCenter
\PositionAngle = Angle
\Outline = #DTA_True
\OutlineColor = $30202020
EndWith
; Create DTA images and draw it on #Image:
DTA1ImageNr = DrawTextAdvanced(@DTA1O)
If DTA1ImageNr
DTA2ImageNr = DrawTextAdvanced(@DTA2O)
If DTA2ImageNr
CreateImage(#Image, #Width, #Height)
StartDrawing(ImageOutput(#Image))
; Draw background gradient:
DrawingMode(#PB_2DDrawing_Gradient)
CircularGradient(#Width / 2 + 5, #Height / 2 + 10, #Width / 2)
GradientColor(0.0, $1381C7)
GradientColor(1.0, $000000)
Box(0, 0, #Width, #Height)
; Draw centered DTA images:
X = Center(DTA1O\PositionWidth, #Width) : Y = Center(DTA1O\PositionHeight, #Height)
DrawAlphaImage(ImageID(DTA2ImageNr), X + 1, Y + 2) ; First the "shadow" image
DrawAlphaImage(ImageID(DTA1ImageNr), X, Y)
StopDrawing()
FreeImage(DTA2ImageNr)
EndIf
FreeImage(DTA1ImageNr)
EndIf
EndProcedure
Procedure CreateExampleImage3()
Protected DTAO.DrawTextAdvanced_Options, DTAImageNr, X, Y
Static InitOptions
Static Width, WidthStep
; "Animate" some options:
If InitOptions = #False
InitOptions = #True
Width = 200 : WidthStep = -2
EndIf
Width + WidthStep
If Width > 350 : WidthStep = -2 : ElseIf Width <= 2 : WidthStep = 2 : EndIf
; Set up the options:
With DTAO
\Text = #Text
\TextFontID = FontID(#Font)
\PositionWidth = Width
EndWith
; Create DTA image and draw it on #Image:
DTAImageNr = DrawTextAdvanced(@DTAO)
If DTAImageNr
CreateImage(#Image, #Width, #Height)
StartDrawing(ImageOutput(#Image))
; Background:
Box(0, 0, #Width, #Height, $FFFFFF)
; Draw line, info text, DTA image and its bounding box:
X = 40 : Y = 50
Box(X + Width, 0, 4, #Height, $808080)
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(0, 0, "Input: " + Str(Width) + "x ?", $808080)
DrawText(0, 20, "Result: " + Str(DTAO\PositionWidth) + "x" + Str(DTAO\PositionHeight), $808080)
DrawAlphaImage(ImageID(DTAImageNr), X, Y)
DrawingMode(#PB_2DDrawing_Outlined)
Box(X - 1, Y - 1, DTAO\PositionWidth + 2, DTAO\PositionHeight + 2, $808080)
StopDrawing()
FreeImage(DTAImageNr)
EndIf
EndProcedure
If OpenWindow(#Window, #PB_Ignore, #PB_Ignore, #Width + 20, #Height + 50, "DrawTextAdvanced Examples", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ImageGadget(#ImageGadget, 10, 10, #Width, #Height, 0)
OptionGadget(#Option1, 10, #Height + 20, 90, 20, "Example 1")
SetGadgetState(#Option1, #True)
OptionGadget(#Option2, 110, #Height + 20, 90, 20, "Example 2")
OptionGadget(#Option3, 210, #Height + 20, 90, 20, "Example 3")
OptionGadget(#Option4, 310, #Height + 20, 90, 20, "Pause")
AddWindowTimer(#Window, #Timer, 50)
Repeat
Select WaitWindowEvent()
Case #PB_Event_Timer
If EventTimer() = #Timer
Select (GetGadgetState(#Option1) * 1) + (GetGadgetState(#Option2) * 2) + (GetGadgetState(#Option3) * 3)
Case 1 : CreateExampleImage1()
Case 2 : CreateExampleImage2()
Case 3 : CreateExampleImage3()
EndSelect
SetGadgetState(#ImageGadget, ImageID(#Image))
EndIf
Case #PB_Event_CloseWindow
Break
EndSelect
ForEver
EndIf
;}
1.13 - 06.02.2011
- Changed: Maybe a bit faster text analyzing by using MidFast()
- Changed: Renamed WrapMargin to WrapSpacing
- Fixed: Too small WrapSpacing caused error
1.10 - 30.01.2011
- Added: 2 more examples that can easily be switched
- Changed: Increased speed of outline code (now uses Demivec's first solution)
- Fixed: If a width was defined the resulting dimension was too big
1.00 - 27.01.2011
Initial release
So I hope this is useful to some of you - Happy coding!
