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