How to add an invisible text layer for search
Draw the text with the rendering mode set to invisible. It becomes a real text object, so search, selection and extraction all find it, but nothing is painted and the scanned image underneath shows through unchanged. This is how a searchable layer is put over OCR output from Google Vision, Tesseract or any other engine that returns words with coordinates.
TextStyle.RenderingMode on a text chunk is the setting, and
TextRenderingModeInvisible is PDF text rendering mode 3.
c := creator.New()
// page comes from a model.PdfReader over the scanned document.
if err := c.AddPage(page); err != nil {
return err
}
for _, word := range words {
sp := c.NewStyledParagraph()
sp.SetPos(word.X, word.Y)
sp.SetFontSize(word.Height)
chunk := sp.SetText(word.Text)
chunk.Style.RenderingMode = creator.TextRenderingModeInvisible
if err := c.Draw(sp); err != nil {
return err
}
}
return c.WriteToFile("searchable.pdf")One paragraph per word is the usual granularity, since that is what OCR engines report and it keeps each string at the coordinates it was recognized at. A whole line in one paragraph works too, but then only the start of the line is positioned and the rest is laid out by the creator, so selection drifts away from the image.
SetPos uses creator coordinates: the origin is the top left of the page and y grows
downward. That matches the image coordinates OCR engines report boxes in, so hOCR
output maps across without conversion. PDF user space runs the other way, from the
bottom left with y upward, so boxes taken from the file itself need c.Height() - y.
How PDF coordinate systems work
covers both.
Every page you want in the output has to be passed to AddPage, including pages you add
no text to. A page you skip does not reach the file.
What to expect from the result
The font still matters even though no glyph is drawn. It decides the width of each string, which is what a viewer highlights when the user drags across the text, so a proportional font over a proportional scan tracks better than a monospace one. It also decides the encoding, so text outside Latin-1 needs a font that covers it, the same as visible text.
Extraction does not filter on rendering mode, so extractor.ExtractText returns the
hidden text exactly as it returns any other. That is the point, and it is also the
reason a badly aligned layer is worth fixing: whatever you place is what everyone gets
when they copy from the page.
For the whole pipeline, from pulling the page images out to posting them to a recognition service and placing the words back, reconstruct PDF from hOCR is the worked example.