24 Oct 2023
Merge PDFs with SVG Texts
Introduction
Submitting a translation for a Revit 2022 file or later generates the 2D views as PDFs. The same can also happen for DWG 2D views, as explained here. We can use this fact to download these PDFs wherever they're available (Bucket, ACC, BIM 360...). Also, if we combine this with a PDF library, such as PDF-LIB we can even modify this PDF by adding SVG on top of it. That's what we'll cover in this sample.
Thank you Nick Boyts for bringing us this idea.
The approach
We are basically taking advantage of three main resources to make this workflow possible:
- PDF 2D views generated from Model Derivative
- Markups extension
- PDF-LIB project
First of all, we need to render the PDF sheet with Viewer and provide a way for the user to add SVGs on top of it. Rendering 2D views is not different from rendering any regular design. For this sample we're passing the urn of a Revit file that only generated a sheet as a derivative, but in case you need to select a 2D view dynamically, you can simply point to that urn and viewguid or have your user load the view through DocumentBrowser extension.
With the 2D view loaded, is time to provide our end user the ability to add SVG. The simplest way to achieve that is with MarkupsCore
and MarkupsGui
extensions, as done through the snippet below in Viewer's initialization:
const config = {
extensions: [
"Autodesk.Viewing.MarkupsCore",
"Autodesk.Viewing.MarkupsGui"
],
};
const viewer = new Autodesk.Viewing.GuiViewer3D(container, config);
As of now, we're only handling the addition of texts as SVG from the Markups extension
With that, once the user draws SVG on top of our PDF, we'll use PDF-LIB to add these SVG generating a new PDF file. We can adapt this documentation in order to add our texts from those generated with Markups
extension.
The main challenge will be correctly positioning and specifying the correct sizes and colors of our fonts.
To properly specify the position, we'll have a logic to convert from Viewer coordinates to PDF-LIB coordinates.
In our specific case, the same PDF has width and height with different values, as in the image below.
To address this compatibility, we have the code below:
function addTextToPDFPage(textHTML, textLine, pdfPage, font) {
// Get the width and height of the first page
const { width, height } = pdfPage.getSize();
//Offsets from the rectangle to the actual text added with Markupd extension
let textOffsetX = parseFloat(textLine.getAttribute("x"));
let textOffsetY = parseFloat(textLine.getAttribute("y"));
let fontSize = parseFloat(textHTML.getAttribute("font-size"));
//example: 'rgba(255,0,0,1)'
let rgba = textHTML.getAttribute("fill");
let red = parseFloat(rgba.split("(")[1].split(",")[0]) / 255;
let green = parseFloat(rgba.split("(")[1].split(",")[1]) / 255;
let blue = parseFloat(rgba.split("(")[1].split(",")[2]) / 255;
//example: 'translate( 6.841478996503916 , 18.88096329583941 ) rotate( 0 ) scale(0.01,-0.01)'
let transform = textHTML.getAttribute("transform");
//Scale applied by Markups extension
let scale = parseFloat(transform.split("scale(")[1].split(",")[0]);
//Rotation applied by Markups extension
let rotation = parseFloat(
transform.split("rotate")[1].split("(")[1].split(")")[0]
);
//Position in Viewer coordinates
let translateX = parseFloat(transform.split("(")[1].split(",")[0]);
let translatey = parseFloat(
transform.split("(")[1].split(",")[1].split(")")[0]
);
//Viewer max coordinates
let modelMaxX = globalViewer.model.getData().modelSpaceBBox.max.x;
let modelMaxY = globalViewer.model.getData().modelSpaceBBox.max.y;
//Relative positioning of text in the sheet varying from 0 to 1
let relativeX = (translateX + textOffsetX * scale) / modelMaxX;
let relativeY = (translatey - textOffsetY * scale) / modelMaxY;
//ratio PDF-LIB/Viewer units
let ratio = width / modelMaxX;
//Positioning of text in the sheet in PDF-LIB coordinates
const finalX = -(0.5 * width) + width * relativeX;
const finalY = -(0.5 * height) + height * relativeY;
// Draw a string of text across the first page
pdfPage.drawText(textLine.innerHTML, {
x: finalX,
y: finalY,
size: fontSize * ratio * scale,
font: font,
color: rgb(red, green, blue),
rotate: degrees(rotation),
});
}
To test the workflow you'll need to first draw a text using Markup extension and then click the Print PDF with Texts
button
And once downloaded you can check the resulting file
Limitations
This sample code only reads the texts added on top of the 2D view.
For this workflow to work, your Revit file needs to be from version 2022 or later, and it needs to be published after November 4th of 2021. This last restriction is because only after this date the extractor started adding the Revit version in the manifest (refer to this blog).
Also, for it to download the PDFs you might need to change your browser settings.
Here's the way to do it in Chrome You can go to Settings > Privacy and Security > Additional Content Settings > PDF documents > Download PDFs
Feel free to try our live demo and check its source code: