Platforms to show: All Mac Windows Linux Cross-Platform
Required plugins for this example: MBS DynaPDF Plugin
You find this example project in your Plugins Download as a Xojo project file within the examples folder: /DynaPDF/Text Positions
This example is the version from Wed, 19th Jan 2021.
Project "Text Positions.xojo_binary_project"
Class App Inherits Application
Const kEditClear = "&Delete"
Const kFileQuit = "&Quit"
Const kFileQuitShortcut = ""
EventHandler Sub NewDocument()
If WindowCount = 0 Then
Dim f As FolderItem = GetOpenFolderItem(FileTypes1.pdf)
If f <> Nil Then
OpenDocument f
End If
End If
End EventHandler
EventHandler Sub Open()
Dim f As FolderItem = FindFile("license.pdf")
If f<> Nil And f.Exists Then
OpenDocument f
End If
Dim ff As FolderItem = SpecialFolder.Desktop.child("test.pdf")
If ff<> Nil And ff.Exists Then
OpenDocument ff
End If
End EventHandler
EventHandler Sub OpenDocument(item As FolderItem)
Dim m As New MainWindow(item)
m.show
End EventHandler
End Class
Class MainWindow Inherits Window
Control CheckSplit Inherits CheckBox
ControlInstance CheckSplit Inherits CheckBox
EventHandler Sub Action()
split = me.Value
canvas1.Invalidate
End EventHandler
End Control
Control Canvas1 Inherits Canvas
ControlInstance Canvas1 Inherits Canvas
EventHandler Sub Paint(g As Graphics, areas() As REALbasic.Rect)
Const factor = 2.0
dim lines() as string
dim Page as DynaPDFPageMBS = pdf.GetPage(1)
dim bbox as DynaPDFRectMBS = page.BBox(page.kpbMediaBox)
dim PageWidth as integer = bbox.Width
Dim PageHeight As Integer = bbox.top - bbox.Bottom
if ShowPDF then
// we draw pdf as background
dim pic as Picture = pdf.RenderPagePicture(1, PageWidth*factor, PageHeight*factor, 2, nil)
g.DrawPicture pic, 0, 0
end if
// now draw text blocks
for each t as PDFText in texts
dim m as DynaPDFMatrixMBS = t.EffectiveMatrix
//XY Axis
g.TextSize = GetScaleY(m) * t.State.FontSize * factor
If t.KerningA <> Nil Then
Dim TextPos As Double = 0
For Each k As DynaPDFTextRecordAMBS In t.KerningA
// translate native encoding to Unicode
Dim decoded As Boolean
Dim ktext As String = k.Text
TextPos = TextPos - k.Advance
Dim TextPosStart As Double = TextPos
Dim i As Integer = 0
Dim l As Integer = ktext.LenB
Dim TextWidth As Double = 0
while i < l
Dim OldTextPos As Double = TextPos
TextWidth = 0
dim ProcessedBytes as integer = 0
Dim OneText As String = DynaPDFMBS.TranslateRawCode(t.state.FontHandle, kText, i, TextWidth, decoded, t.state.CharSpacing, t.state.WordSpacing, t.State.TextScale, ProcessedBytes)
lines.append OneText+": "+Str(width)
if width <= 0.0 then
Break // something broken?
end if
If ProcessedBytes <= 0 Then
Break // should not happen
ProcessedBytes = 1
End If
TextPos = TextPos + TextWidth
// drawing here will not match what DynaPDF draws as we do not take style and spacing in account
g.ForeColor = &c00FF00
g.PenWidth = 1
// calculate all 4 points
// top left
Dim x1 As Double = OldTextPos
Dim y1 As Double = 0.0
m.Transform(x1,y1)
// bottom left
Dim x2 As Double = OldTextPos
Dim y2 As Double = t.State.FontSize
m.Transform(x2,y2)
// bottom right
Dim x3 As Double = TextPos
Dim y3 As Double = t.State.FontSize
m.Transform(x3,y3)
// top right
Dim x4 As Double = TextPos
Dim y4 As Double = 0
m.Transform(x4,y4)
If Split Then
// draw rectangle (could be rotated or skewed)
g.DrawLine x1*factor, (PageHeight-y1)*factor, x2*factor, (PageHeight-y2)*factor
g.DrawLine x2*factor, (PageHeight-y2)*factor, x3*factor, (PageHeight-y3)*factor
g.DrawLine x3*factor, (PageHeight-y3)*factor, x4*factor, (PageHeight-y4)*factor
g.DrawLine x4*factor, (PageHeight-y4)*factor, x1*factor, (PageHeight-y1)*factor
End If
g.ForeColor = &c0000FF
g.TextSize = CalcDistance(x1,y1,x2,y2)*factor
Dim y As Double = Min(y1,y2) // on the bottom side we draw text.
Dim x As Double = (x3+x1) / 2.0 // middle of the text block for our text
Dim tw As Double = g.StringWidth(OneText)
g.DrawString OneText, x * factor - tw/2, (PageHeight-y)*factor
i = i + ProcessedBytes
wend
if not split then
// box around
g.PenWidth = 1
g.ForeColor = &cFF0000
// calculate all 4 points
// top left
Dim x1 As Double = TextPosStart
Dim y1 As Double = 0.0
m.Transform(x1,y1)
// bottom left
Dim x2 As Double = TextPosStart
Dim y2 As Double = t.State.FontSize
m.Transform(x2,y2)
// bottom right
Dim x3 As Double = TextPos
Dim y3 As Double = t.State.FontSize
m.Transform(x3,y3)
// top right
Dim x4 As Double = TextPos
Dim y4 As Double = 0
m.Transform(x4,y4)
g.DrawLine x1*factor, (PageHeight-y1)*factor, x2*factor, (PageHeight-y2)*factor
g.DrawLine x2*factor, (PageHeight-y2)*factor, x3*factor, (PageHeight-y3)*factor
g.DrawLine x3*factor, (PageHeight-y3)*factor, x4*factor, (PageHeight-y4)*factor
g.DrawLine x4*factor, (PageHeight-y4)*factor, x1*factor, (PageHeight-y1)*factor
end if
next
end if
next
End EventHandler
End Control
Control CheckShowPDF Inherits CheckBox
ControlInstance CheckShowPDF Inherits CheckBox
EventHandler Sub Action()
ShowPDF = me.Value
canvas1.Invalidate
End EventHandler
End Control
EventHandler Sub Open()
dim p as new MyDynapdfMBS
p.SetLicenseKey "Pro" // For this example you can use a Pro or Enterprise License
call p.CreateNewPDF(Nil)
// Skip anything that is not required
call p.SetImportFlags p.kifImportAll+p.kifImportAsPage
// From which PDF file do you want to extract the images?
call p.OpenImportFile(self.file, p.kptOpen, "")
// Comment this out if you want to extract the images from specific pages only
call p.ImportPDFFile(1, 1.0, 1.0)
call p.CloseImportFile
dim parser as new MyDynaPDFParseInterfaceMBS(p)
'If you want to create a multipage TIFF then create the output image here
'and call AddImage() only in the callback function. After the loop
'returns call CloseImage() to close the image file.
call p.EditPage 1
call p.ParseContent(parser, p.kpfNone)
call p.EndPage
self.texts = parser.Texts
self.pdf = p
End EventHandler
Private Function CalcDistance(x1 as double, y1 as Double, x2 as Double, y2 as Double) As Double
// Distance between two points
dim dx as double = x2-x1
dim dy as double = y2-y1
return sqrt(dx*dx + dy*dy)
End Function
Sub Constructor(file as FolderItem)
Self.file = file
Self.Title = file.DisplayName
// Calling the overridden superclass constructor.
Super.Constructor
End Sub
Private Function GetScaleX(m As DynapdfMatrixMBS) As Double
Dim x1 As double= 0.0
Dim y1 As double= 0.0
Dim x2 As double= 1.0
Dim y2 As double= 0.0
Transform(m, x1, y1)
Transform(m, x2, y2)
if (y1 > y2) then
return -CalcDistance(x1, y1, x2, y2)
else
return CalcDistance(x1, y1, x2, y2)
end if
End Function
Private Function GetScaleY(m As DynapdfMatrixMBS) As Double
Dim x1 As double= 0.0
Dim y1 As double= 0.0
Dim x2 As double= 0.0
Dim y2 As double= 1.0
Transform(m, x1, y1)
Transform(m, x2, y2)
if (y1 > y2) then
return -CalcDistance(x1, y1, x2, y2)
else
return CalcDistance(x1, y1, x2, y2)
end if
End Function
Property ShowPDF As Boolean = true
Property file As FolderItem
Property pdf As MyDynaPDFMBS
Property split As Boolean
Property texts() As PDFText
End Class
MenuBar MainMenuBar
MenuItem FileMenu = "&File"
MenuItem FileQuit = "#App.kFileQuit"
MenuItem EditMenu = "&Edit"
MenuItem EditUndo = "&Undo"
MenuItem EditSeparator1 = "-"
MenuItem EditCut = "Cu&t"
MenuItem EditCopy = "&Copy"
MenuItem EditPaste = "&Paste"
MenuItem EditClear = "#App.kEditClear"
MenuItem EditSeparator2 = "-"
MenuItem EditSelectAll = "Select &All"
End MenuBar
Class PDFState
Sub Constructor(other as PDFState)
self.CharSpacing = other.CharSpacing
self.ExtGState = other.ExtGState
self.FontHandle = other.FontHandle
self.FontInfo = other.FontInfo
self.FontName = other.FontName
self.FontSize = other.FontSize
self.FontStyle = other.FontStyle
self.MiterLimit = other.MiterLimit
self.textDrawMode = other.textDrawMode
self.TextScale = other.TextScale
self.WordSpacing = other.WordSpacing
self.matrix = new DynaPDFMatrixMBS(other.matrix) // copy
End Sub
Sub constructor()
matrix = new DynaPDFMatrixMBS // makes new identity matrix
FontSize = 1.0
CharSpacing = 0.0
TextDrawMode = DynaPDFMBS.kdmNormal
TextScale = 100.0
WordSpacing = 0.0
End Sub
Property CharSpacing As Double
Property ExtGState As DynaPDFExtGState2MBS
Property FontHandle As Integer
Property FontInfo As DynaPDFFontInfoMBS
Property FontName As String
Property FontSize As double
Property FontStyle As Integer
Property Leading As Double
Property MiterLimit As double
Property TextScale As Double
Property WordSpacing As Double
Property matrix As DynaPDFMatrixMBS
Property textDrawMode As Integer
End Class
Class MyDynaPDFMBS Inherits DynaPDFMBS
EventHandler Function Error(ErrorCode as integer, ErrorMessage as string, ErrorType as integer) As integer
// output all messages on the console:
System.DebugLog str(ErrorCode)+": "+ErrorMessage
'// and display dialog:
'Dim d as New MessageDialog //declare the MessageDialog object
'Dim b as MessageDialogButton //for handling the result
'
'd.icon=MessageDialog.GraphicCaution //display warning icon
'd.ActionButton.Caption="Continue"
'd.CancelButton.Visible=True //show the Cancel button
'
'// a warning or an error?
'
'if BitAnd(ErrorType, me.kE_WARNING) = me.kE_WARNING then
'// if user decided to ignore, we'll ignore
'if IgnoreWarnings then Return 0
'
'd.Message="A warning occurred while processing your PDF code."
'
'// we add a third button to display all warnings
'd.AlternateActionButton.Caption = "Ignore warnings"
'd.AlternateActionButton.Visible = true
'else
'd.Message="An error occurred while processing your PDF code."
'end if
'
'd.Explanation = str(ErrorCode)+": "+ErrorMessage
'
'b=d.ShowModal //display the dialog
'
'Select Case b //determine which button was pressed.
'Case d.ActionButton
'Return 0 // ignore
'Case d.AlternateActionButton
'IgnoreWarnings = true
'Return 0 // ignore
'Case d.CancelButton
'Return -1 // stop
'End select
'
End EventHandler
Property IgnoreWarnings As Boolean
End Class
Class MyDynaPDFParseInterfaceMBS Inherits DynaPDFParseInterfaceMBS
EventHandler Function BeginTemplate(ObjectPtr as integer, Handle as integer, BBox as DynaPDFRectMBS, Matrix as DynaPDFMatrixMBS) As integer
If matrix <> Nil Then
current.matrix = current.matrix * matrix
End If
End EventHandler
EventHandler Sub MulMatrix(ObjectPtr as integer, matrix as DynaPDFMatrixMBS)
If matrix <> Nil Then
current.matrix = current.matrix * matrix
Else
Break // should not happen
End If
End EventHandler
EventHandler Function RestoreGraphicState() As integer
current = states.pop
End EventHandler
EventHandler Function SaveGraphicState() As integer
states.Append current
current = new PDFState(Current)
End EventHandler
EventHandler Sub SetCharSpacing(ObjectPtr as integer, Value as double)
current.CharSpacing = value
End EventHandler
EventHandler Sub SetExtGState(ObjectPtr as integer, GS as DynaPDFExtGState2MBS)
current.ExtGState = gs
End EventHandler
EventHandler Sub SetFont(ObjectPtr as integer, fontType as integer, Embedded as boolean, FontName as string, Style as integer, FontSize as double, FontHandle as integer, FontInfo as DynaPDFFontInfoMBS)
current.FontName = FontName
current.FontStyle = Style
current.FontSize = FontSize
current.FontInfo = FontInfo
current.FontHandle = FontHandle
End EventHandler
EventHandler Sub SetLeading(ObjectPtr as integer, Value as double)
current.Leading = value
End EventHandler
EventHandler Sub SetMiterLimit(ObjectPtr as integer, Value as double)
current.MiterLimit = value
End EventHandler
EventHandler Sub SetTextDrawMode(ObjectPtr as integer, Mode as integer)
current.textDrawMode = mode
End EventHandler
EventHandler Sub SetTextScale(ObjectPtr as integer, Value as double)
current.TextScale = value
End EventHandler
EventHandler Sub SetWordSpacing(ObjectPtr as integer, Value as double)
current.WordSpacing = value
End EventHandler
EventHandler Function ShowTextArrayA(ObjectPtr as integer, Matrix as DynaPDFMatrixMBS, Kerning() as DynaPDFTextRecordAMBS, Count as integer, Width as double) As integer
dim t as new PDFText
t.State = new PDFState(current)
t.Count = Count
t.Width = Width
t.KerningA = Kerning
t.Matrix = matrix
texts.Append t
End EventHandler
Sub Constructor(p as MyDynaPDFMBS)
self.current = new PDFState
self.pdf = p
End Sub
Property States() As PDFState
Property Texts() As PDFText
Property current As PDFState
Property pdf As dynaPDFmbs
End Class
Module UtilModule
Function FindFile(name as string) As FolderItem
// Look for file in parent folders from executable on
dim parent as FolderItem = app.ExecutableFile.Parent
while parent<>Nil
dim file as FolderItem = parent.Child(name)
if file<>Nil and file.Exists then
Return file
end if
parent = parent.Parent
wend
End Function
Sub Transform(m as DynaPDFMatrixMBS, byref x as Double, byref y as double)
dim tx as double = x
x = tx * M.a + y * M.c + M.x
y = tx * M.b + y * M.d + M.y
End Sub
End Module
Class PDFText
Function EffectiveMatrix() As DynaPDFMatrixMBS
return matrix * state.matrix
End Function
Property Count As Integer
Property Decoded As boolean
Property KerningA() As DynapdfTextRecordAMBS
Property Matrix As DynapdfMatrixMBS
Property Source() As DynapdfTextRecordAMBS
Property State As PDFState
Property Width As Double
End Class
FileTypes1
Filetype application/pdf
End FileTypes1
End Project
See also:
- /DynaPDF/text editing/delete text
- /DynaPDF/text editing/edit text
- /DynaPDF/text editing/replace text
- /DynaPDF/Text extraction
- /DynaPDF/text formatting/text formatting Ansi
- /DynaPDF/text formatting/text formatting parallel
- /DynaPDF/text formatting/text formatting Unicode
- /DynaPDF/Text Positions with parser
The items on this page are in the following plugins: MBS DynaPDF Plugin.