1 #include "export.h"
2 #include "polyset.h"
3 #include "polyset-utils.h"
4 #include "printutils.h"
5 #include "version.h"
6 #include "version_helper.h"
7 
8 #include <string>
9 #include <cmath>
10 
11 #ifdef ENABLE_CAIRO
12 
13 #include <cairo.h>
14 #include <cairo-pdf.h>
15 
16 
17 // A4 Size Paper
18 #define WPOINTS 595.
19 #define HPOINTS 842.
20 #define MARGIN 30.
21 
get_cairo_version()22 const std::string get_cairo_version() {
23 	return OpenSCAD::get_version(CAIRO_VERSION_STRING, cairo_version_string());
24 }
25 
26 enum class OriginPosition{
27   BUTTOMLEFT,
28   CENTER
29 };
30 
draw_text(const char * text,cairo_t * cr,double x,double y,double fontSize)31 void draw_text(const char *text, cairo_t *cr,double x,double y, double fontSize){
32 
33     cairo_set_font_size(cr, fontSize);
34     cairo_move_to(cr,x,y);
35     cairo_show_text(cr,text);
36 
37 }
38 
mm_to_points(double mm)39 double mm_to_points(double mm){
40     return mm*2.8346;
41 }
42 
draw_axis(cairo_t * cr,OriginPosition pos)43 void draw_axis(cairo_t *cr, OriginPosition pos){
44     cairo_set_font_size(cr, 6.);
45     cairo_set_line_width(cr, 0.4);
46     double offset = mm_to_points(10.);
47 
48     if(pos == OriginPosition::CENTER){
49 
50         for(int i=0;i<10;i++){
51             cairo_move_to(cr, i*offset, (HPOINTS/2.));
52             cairo_line_to(cr, i*offset, (HPOINTS/2.)-mm_to_points(12));
53             cairo_stroke(cr);
54             cairo_move_to(cr,(i*offset)+1.5, (HPOINTS/2.)-mm_to_points(8));
55             if(i%2==0){
56                 std::string num = std::to_string(i*10);
57                 cairo_show_text(cr, num.c_str());
58             }
59         }
60         for(int i=1;i<10;i++){
61             cairo_move_to(cr, -i*offset, (HPOINTS/2.));
62             cairo_line_to(cr, -i*offset, (HPOINTS/2.)-mm_to_points(12));
63             cairo_stroke(cr);
64             cairo_move_to(cr,(-i*offset)+1.5, (HPOINTS/2.)-mm_to_points(8));
65             if(i%2==0){
66                 std::string num = std::to_string(-i*10);
67                 cairo_show_text(cr, num.c_str());
68             }
69         }
70 
71         for(int i=0;i<15;i++){
72             cairo_move_to(cr, -(WPOINTS/2.) , -i*offset);
73             cairo_line_to(cr, -(WPOINTS/2.)+mm_to_points(12), -i*offset);
74             cairo_stroke(cr);
75             cairo_move_to(cr, -(WPOINTS/2.)+mm_to_points(8), (-i*offset)-1.5);
76             if(i%2==0){
77                 std::string num = std::to_string(i*10);
78                 cairo_show_text(cr, num.c_str());
79             }
80         }
81         for(int i=1;i<15;i++){
82             cairo_move_to(cr, -(WPOINTS/2.), i*offset);
83             cairo_line_to(cr, -(WPOINTS/2.)+mm_to_points(12), i*offset);
84             cairo_stroke(cr);
85             cairo_move_to(cr, -(WPOINTS/2.)+mm_to_points(8), (i*offset)-1.5);
86             if(i%2==0){
87                 std::string num = std::to_string(-i*10);
88                 cairo_show_text(cr, num.c_str());
89             }
90         }
91         cairo_set_source_rgba(cr, 0., 0., 0., 1.0);
92         cairo_move_to(cr, 0., (HPOINTS/2.));
93         cairo_line_to(cr, 0., (HPOINTS/2.)-mm_to_points(12));
94         cairo_move_to(cr, -(WPOINTS/2.), 0.);
95         cairo_line_to(cr, -(WPOINTS/2.)+mm_to_points(12), 0.);
96         cairo_stroke(cr);
97 
98     }else{
99 
100         for(int i=1;i<20;i++){
101             cairo_move_to(cr, i*offset, 0.0);
102             cairo_line_to(cr, i*offset, -mm_to_points(12));
103             cairo_stroke(cr);
104             cairo_move_to(cr,(i*offset)+1.5, -mm_to_points(8));
105             if((i-1)%2==0){
106                 std::string num = std::to_string((i-1)*10);
107                 cairo_show_text(cr, num.c_str());
108             }
109         }
110         for(int i=1;i<30;i++){
111             cairo_move_to(cr, 0., -i*offset);
112             cairo_line_to(cr, mm_to_points(12), -i*offset);
113             cairo_stroke(cr);
114             cairo_move_to(cr, mm_to_points(8), (-i*offset)-1.5);
115             if((i-1)%2==0){
116                 std::string num = std::to_string((i-1)*10);
117                 cairo_show_text(cr, num.c_str());
118             }
119         }
120     }
121 }
122 
draw_geom(const Polygon2d & poly,cairo_t * cr,bool & inpaper,OriginPosition pos)123 void draw_geom(const Polygon2d &poly, cairo_t *cr, bool &inpaper, OriginPosition pos){
124     for(const auto &o : poly.outlines()){
125         if (o.vertices.empty()) {
126             continue;
127         }
128         const Eigen::Vector2d& p0 = o.vertices[0];
129         cairo_move_to(cr, mm_to_points(p0.x()), mm_to_points(-p0.y()));
130         for (unsigned int idx = 1;idx < o.vertices.size();idx++) {
131             const Eigen::Vector2d& p = o.vertices[idx];
132             cairo_line_to(cr, mm_to_points(p.x()), mm_to_points(-p.y()));
133             if(pos == OriginPosition::CENTER){
134                 if( abs((int)mm_to_points(p.x()))>(WPOINTS/2) || abs((int)mm_to_points(p.y()))>(HPOINTS/2)) {
135                     inpaper = false;
136                 }
137             }else {
138                 if( abs((int)mm_to_points(p.x()))>WPOINTS || abs((int)mm_to_points(p.y()))>HPOINTS) {
139                     inpaper = false;
140                 }
141             }
142         }
143         cairo_line_to(cr, mm_to_points(p0.x()), mm_to_points(-p0.y()));
144 
145     }
146 }
147 
draw_geom(const shared_ptr<const Geometry> & geom,cairo_t * cr,bool & inpaper,OriginPosition pos)148 void draw_geom(const shared_ptr<const Geometry> &geom, cairo_t *cr, bool &inpaper, OriginPosition pos){
149     if (const auto geomlist = dynamic_pointer_cast<const GeometryList>(geom)) {
150         for (const auto &item : geomlist->getChildren()) {
151             draw_geom(item.second, cr, inpaper, pos);
152         }
153     }
154     else if (dynamic_pointer_cast<const PolySet>(geom)) {
155         assert(false && "Unsupported file format");
156     }
157     else if (const auto poly = dynamic_pointer_cast<const Polygon2d>(geom)) {
158         draw_geom(*poly, cr, inpaper, pos);
159     } else {
160         assert(false && "Export as PDF for this geometry type is not supported");
161     }
162 }
163 
export_pdf_write(void * closure,const unsigned char * data,unsigned int length)164 static cairo_status_t export_pdf_write(void *closure, const unsigned char *data, unsigned int length)
165 {
166 	std::ostream *stream = static_cast<std::ostream *>(closure);
167 	stream->write(reinterpret_cast<const char *>(data), length);
168 	return !(*stream) ? CAIRO_STATUS_WRITE_ERROR : CAIRO_STATUS_SUCCESS;
169 }
170 
export_pdf(const shared_ptr<const Geometry> & geom,std::ostream & output,const ExportInfo & exportInfo)171 void export_pdf(const shared_ptr<const Geometry> &geom, std::ostream &output, const ExportInfo& exportInfo)
172 {
173     cairo_surface_t *surface = cairo_pdf_surface_create_for_stream(export_pdf_write, &output, WPOINTS, HPOINTS);
174     if(cairo_surface_status(surface)==cairo_status_t::CAIRO_STATUS_NULL_POINTER){
175         cairo_surface_destroy(surface);
176         return;
177     }
178 
179 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
180 	cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_TITLE, exportInfo.sourceFileName.c_str());
181 	cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATOR, "OpenSCAD (https://www.openscad.org/)");
182 	cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATE_DATE, "");
183 	cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_MOD_DATE, "");
184 #endif
185 
186     cairo_t *cr = cairo_create(surface);
187 
188     cairo_set_source_rgba(cr, 0., 0., 0., 1.0);
189     cairo_set_line_width(cr, 1);
190 
191     BoundingBox bbox = geom->getBoundingBox();
192     int minx = (int)floor(bbox.min().x());
193     int maxy = (int)floor(bbox.max().y());
194     int maxx = (int)ceil(bbox.max().x());
195     int miny = (int)ceil(bbox.min().y());
196 
197     bool inpaper = true;
198     std::string about = "Scale is to calibrate actual printed dimension. Check both X and Y. Measure between tick 0 and last tick";
199 
200     if(minx>=0 && miny>=0 && maxx>=0 && maxy>=0){
201 
202         cairo_translate(cr, MARGIN, HPOINTS-MARGIN);
203         draw_geom(geom, cr, inpaper, OriginPosition::BUTTOMLEFT);
204         cairo_stroke(cr);
205         cairo_set_source_rgba(cr, 0., 0., 0., 0.4);
206         draw_text(exportInfo.sourceFilePath.c_str(),cr, 0., -HPOINTS+(2.*MARGIN), 10.);
207         cairo_translate(cr, -MARGIN, MARGIN);
208         draw_axis(cr, OriginPosition::BUTTOMLEFT);
209         draw_text(about.c_str(), cr, mm_to_points(13), -mm_to_points(6), 5.);
210 
211     } else {
212 
213         cairo_translate(cr, WPOINTS/2., HPOINTS/2.);
214         draw_geom(geom, cr, inpaper, OriginPosition::CENTER);
215         cairo_stroke(cr);
216         cairo_set_source_rgba(cr, 0., 0., 0., 0.4);
217         draw_text(exportInfo.sourceFilePath.c_str(),cr, -(WPOINTS/2.)+MARGIN, -(HPOINTS/2.)+MARGIN, 10.);
218         draw_axis(cr, OriginPosition::CENTER);
219         cairo_set_source_rgba(cr, 0., 0., 0., 0.4);
220         draw_text(about.c_str(), cr, -(WPOINTS/2.)+mm_to_points(13), (HPOINTS/2.)-mm_to_points(6), 5.);
221 
222     }
223 
224     if (!inpaper) {
225         LOG(message_group::Export_Warning, Location::NONE, "", "Geometry is too large to fit into A4 size.");
226     }
227 
228     cairo_show_page(cr);
229     cairo_surface_destroy(surface);
230     cairo_destroy(cr);
231 
232 }
233 #else //ENABLE_CAIRO
234 
get_cairo_version()235 const std::string get_cairo_version() {
236 	const std::string cairo_version = "(not enabled)";
237 	return cairo_version;
238 }
239 
export_pdf(const shared_ptr<const Geometry> &,std::ostream &,const ExportInfo &)240 void export_pdf(const shared_ptr<const Geometry>&, std::ostream&, const ExportInfo&) {
241 
242     LOG(message_group::Error, Location::NONE, "", "Export to PDF format was not enabled when building the application.");
243 
244 }
245 
246 #endif //ENABLE_CAIRO
247