How can I enlarge the thin, zero-width lines of a PDF?

Q:

Some documents, such as CAD files, can have lots of very thin lines in them. Oftentimes, this is caused by zero-width lines, which my users can find hard to see even when zoomed in. Is there any way to make lines larger, so that they’re easier to read?

A:

The following C# code can be used to expand the line widths of paths, as well as characters of Type3 fonts:

//
// Copyright © 2001-2020 by PDFTron Systems Inc. All Rights Reserved.
//

using System;
using System.Collections.Generic;

using pdftron;
using pdftron.Common;
using pdftron.Filters;
using pdftron.SDF;
using pdftron.PDF;

using XSet = System.Collections.Generic.List;
using EltCase = System.Action<pdftron.PDF.ElementReader, pdftron.PDF.ElementWriter, pdftron.PDF.Element>;

namespace ElementEditTestCS
{
class Class1
{
private static pdftron.PDFNetLoader pdfNetLoader = pdftron.PDFNetLoader.Instance();
static Class1() {}

static void ProcessElements(ElementReader reader, ElementWriter writer, XSet visited,
EltCase path, EltCase text, EltCase form)
{
Element element;
while ((element = reader.Next()) != null)
{
switch (element.GetType())
{
case Element.Type.e_path:
path(reader, writer, element);
break;
case Element.Type.e_text:
text(reader, writer, element);
break;
case Element.Type.e_form:
{
writer.WriteElement(element);
Obj form_obj = element.GetXObject();
if (!visited.Contains(form_obj.GetObjNum()))
{
visited.Add(form_obj.GetObjNum());
ElementWriter new_writer = new ElementWriter();
reader.FormBegin();
new_writer.Begin(form_obj, true);
reader.ClearChangeList();
new_writer.SetDefaultGState(reader);
form(reader, writer, element);
new_writer.End();
reader.End();
}
break;
}
default:
writer.WriteElement(element);
break;
}
}
}

static EltCase EnlargeZeroWidthLines(int new_width)
{
return (reader, writer, element) =>
{
GState gs = element.GetGState();
if (gs.GetLineWidth() < 0.001) gs.SetLineWidth(new_width);
writer.WriteElement(element);
};
}

static void EnlargeCharacters(ElementReader reader_, ElementWriter writer_, XSet visited_)
{
ProcessElements(reader_, writer_, visited_,
EnlargeZeroWidthLines(10), // make zero-width lines in characters 10 units wide (better appearance)
(reader, writer, element) => { writer.WriteElement(element); },
(reader, writer, element) => { EnlargeCharacters(reader, writer, visited_); });
}

static void RewriteCharacters(PDFDoc doc, XSet charprocs)
{
ElementWriter writer = new ElementWriter();
ElementReader reader = new ElementReader();
XSet visited = new XSet();

foreach (int charproc in charprocs)
{
SDFDoc sdf = doc.GetSDFDoc();
reader.Begin(sdf.GetObj(charproc));
writer.Begin(sdf);
EnlargeCharacters(reader, writer, visited);
reader.End();
sdf.Swap(charproc, writer.End().GetObjNum());
}
}

static XSet EnlargePaths(ElementReader reader_, ElementWriter writer_, XSet visited_)
{
XSet charprocs = new XSet();
ProcessElements(reader_, writer_, visited_,
EnlargeZeroWidthLines(1), // make zero-width paths one unit wide
(reader, writer, element) =>
{
writer.WriteElement(element);
Obj font_dict = element.GetGState().GetFont().GetSDFObj();
if (font_dict.Get(“Subtype”).Value().GetName() != “Type3”) return;
DictIterator itr = font_dict.Get(“CharProcs”).Value().GetDictIterator();
while (itr.HasNext())
{
int n = itr.Value().GetObjNum();
if (!charprocs.Contains(n)) charprocs.Add(n);
itr.Next();
}
},
(reader, writer, element) => { charprocs.AddRange(EnlargePaths(reader, writer, visited_)); });
return charprocs;
}

static XSet RewritePages(PDFDoc doc)
{
ElementWriter writer = new ElementWriter();
ElementReader reader = new ElementReader();
XSet visited = new XSet();
XSet charprocs = new XSet();

PageIterator itr = doc.GetPageIterator();
while (itr.HasNext())
{
Page page = itr.Current();
visited.Add(page.GetSDFObj().GetObjNum());
reader.Begin(page);
writer.Begin(page, ElementWriter.WriteMode.e_replacement, false, true, page.GetResourceDict());
charprocs.AddRange(EnlargePaths(reader, writer, visited));
writer.End();
reader.End();
itr.Next();
}

return charprocs; // all type3 font characters (each might contain zero-width lines)
}

[STAThread]
static void Main(string[] args)
{
PDFNet.Initialize();

string input_path = “…/…/TestFiles/”;
string output_path = “…/…/TestFiles/Output/”;
string input_filename = //…
string output_filename = //…

try
{
Console.WriteLine(“Opening the input file…”);
using (PDFDoc doc = new PDFDoc(input_path + input_filename))
{
doc.InitSecurityHandler();
XSet charprocs = RewritePages(doc);
RewriteCharacters(doc, charprocs);
doc.Save(output_path + output_filename, SDFDoc.SaveOptions.e_remove_unused);
Console.WriteLine(“Done. Result saved in {0}…”, output_filename);
}
}
catch (PDFNetException e)
{
Console.WriteLine(e.Message);
}
}
}
}