1 /*
2  * Copyright 2014 Adobe Systems Incorporated. All rights reserved.
3  * Copyright 2017 Khaled Hosny
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use these files except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #define PY_SSIZE_T_CLEAN 1
19 #include <Python.h>
20 
21 #include <stdbool.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "psautohint.h"
27 
28 static void
reportCB(char * msg,int level)29 reportCB(char* msg, int level)
30 {
31     static PyObject* logger = NULL;
32 
33     if (logger == NULL) {
34         PyObject* logging = PyImport_ImportModule("logging");
35         if (logging == NULL)
36             return;
37         logger = PyObject_CallMethod(logging, "getLogger", "s", "_psautohint");
38         if (logger == NULL)
39             return;
40     }
41 
42     switch (level) {
43         case AC_LogDebug:
44             PyObject_CallMethod(logger, "debug", "s", msg);
45             break;
46         case AC_LogInfo:
47             PyObject_CallMethod(logger, "info", "s", msg);
48             break;
49         case AC_LogWarning:
50             PyObject_CallMethod(logger, "warning", "s", msg);
51             break;
52         case AC_LogError:
53             PyObject_CallMethod(logger, "error", "s", msg);
54             break;
55         default:
56             break;
57     }
58 }
59 
60 static void
charZoneCB(float top,float bottom,char * glyphName,void * userData)61 charZoneCB(float top, float bottom, char* glyphName, void* userData)
62 {
63     ACBufferWriteF((ACBuffer*)userData, "charZone %s top %f bottom %f\n",
64                    glyphName, (double)top, (double)bottom);
65 }
66 
67 static void
stemZoneCB(float top,float bottom,char * glyphName,void * userData)68 stemZoneCB(float top, float bottom, char* glyphName, void* userData)
69 {
70     ACBufferWriteF((ACBuffer*)userData, "stemZone %s top %f bottom %f\n",
71                    glyphName, (double)top, (double)bottom);
72 }
73 
74 static void
hstemCB(float top,float bottom,char * glyphName,void * userData)75 hstemCB(float top, float bottom, char* glyphName, void* userData)
76 {
77     ACBufferWriteF((ACBuffer*)userData, "HStem %s top %f bottom %f\n",
78                    glyphName, (double)top, (double)bottom);
79 }
80 
81 static void
vstemCB(float right,float left,char * glyphName,void * userData)82 vstemCB(float right, float left, char* glyphName, void* userData)
83 {
84     ACBufferWriteF((ACBuffer*)userData, "VStem %s right %f left %f\n",
85                    glyphName, (double)right, (double)left);
86 }
87 
88 static void
reportRetry(void * userData)89 reportRetry(void* userData)
90 {
91     ACBufferReset((ACBuffer*)userData);
92 }
93 
94 static void*
memoryManager(void * ctx,void * ptr,size_t size)95 memoryManager(void* ctx, void* ptr, size_t size)
96 {
97     if (!ptr && !size)
98         return NULL;
99 
100     if (ptr && size)
101         ptr = PyMem_RawRealloc(ptr, size);
102     else if (size)
103         ptr = PyMem_RawCalloc(1, size);
104     else
105         PyMem_RawFree(ptr);
106 
107     return ptr;
108 }
109 
110 static PyObject* PsAutoHintError;
111 
112 static char autohint_doc[] =
113   "Autohint glyphs.\n"
114   "\n"
115   "Signature:\n"
116   "  autohint(font_info, glyphs[, no_edit, allow_hint_sub, round])\n"
117   "\n"
118   "Args:\n"
119   "  font_info: font information.\n"
120   "  glyph: glyph data in bez format.\n"
121   "  allow_edit: allow editing (changing) the paths when hinting.\n"
122   "  allow_hint_sub: no multiple layers of coloring.\n"
123   "  round: round coordinates.\n"
124   "\n"
125   "Output:\n"
126   "  Autohinted glyph data in bez format.\n"
127   "\n"
128   "Raises:\n"
129   "  psautohint.error: If autohinting fails.\n";
130 
131 static PyObject*
autohint(PyObject * self,PyObject * args)132 autohint(PyObject* self, PyObject* args)
133 {
134     int allowEdit = true, roundCoords = true, allowHintSub = true;
135     int report = 0, allStems = false;
136     PyObject* fontObj = NULL;
137     PyObject* inObj = NULL;
138     PyObject* outObj = NULL;
139     char* inData = NULL;
140     char* fontInfo = NULL;
141     bool error = true;
142     ACBuffer* reportBuffer = NULL;
143 
144     if (!PyArg_ParseTuple(args, "O!O!|iiiii", &PyBytes_Type, &fontObj,
145                           &PyBytes_Type, &inObj, &allowEdit, &allowHintSub,
146                           &roundCoords, &report, &allStems))
147         return NULL;
148 
149     if (report) {
150         reportBuffer = ACBufferNew(150);
151         allowEdit = allowHintSub = false;
152         switch (report) {
153             case 1:
154                 AC_SetReportRetryCB(reportRetry, (void*)reportBuffer);
155                 AC_SetReportZonesCB(charZoneCB, stemZoneCB,
156                                     (void*)reportBuffer);
157                 break;
158             case 2:
159                 AC_SetReportRetryCB(reportRetry, (void*)reportBuffer);
160                 AC_SetReportStemsCB(hstemCB, vstemCB, allStems,
161                                     (void*)reportBuffer);
162                 break;
163             default:
164                 PyErr_SetString(PyExc_ValueError,
165                                 "Invalid \"report\" argument, must be 1 or 2");
166                 goto done;
167         }
168     }
169 
170     AC_SetMemManager(NULL, memoryManager);
171     AC_SetReportCB(reportCB);
172 
173     fontInfo = PyBytes_AsString(fontObj);
174     inData = PyBytes_AsString(inObj);
175     if (inData && fontInfo) {
176         int result = -1;
177 
178         ACBuffer* output = ACBufferNew(4 * strlen(inData));
179         if (output) {
180             result = AutoHintString(inData, fontInfo, output, allowEdit,
181                                     allowHintSub, roundCoords);
182 
183             if (result == AC_Success) {
184                 char* data;
185                 size_t len;
186                 error = false;
187                 if (reportBuffer)
188                     ACBufferRead(reportBuffer, &data, &len);
189                 else
190                     ACBufferRead(output, &data, &len);
191                 outObj = PyBytes_FromStringAndSize(data, len);
192             }
193         }
194         ACBufferFree(output);
195         output=NULL;
196 
197         if (result != AC_Success) {
198             switch (result) {
199                 case -1:
200                     /* Do nothing, we already called PyErr_* */
201                     break;
202                 case AC_FatalError:
203                     PyErr_SetString(PsAutoHintError, "Fatal error");
204                     break;
205                 case AC_InvalidParameterError:
206                     PyErr_SetString(PyExc_ValueError, "Invalid glyph data");
207                     break;
208                 case AC_UnknownError:
209                 default:
210                     PyErr_SetString(PsAutoHintError, "Hinting failed");
211                     break;
212             }
213         }
214     }
215 
216 done:
217     ACBufferFree(reportBuffer);
218     reportBuffer = NULL;
219     AC_initCallGlobals(); /* clear out references to reportBuffer */
220 
221     if (error)
222         return NULL;
223     return outObj;
224 }
225 
226 static char autohintmm_doc[] =
227   "Autohint glyphs.\n"
228   "\n"
229   "Signature:\n"
230   "  autohintm(font_info, glyphs)\n"
231   "\n"
232   "Args:\n"
233   "  glyphs: sequence of glyph data in bez format.\n"
234   "  masters: sequence of master names.\n"
235   "\n"
236   "Output:\n"
237   "  Sequence of autohinted glyph data in bez format.\n"
238   "\n"
239   "Raises:\n"
240   "  psautohint.error: If autohinting fails.\n";
241 
242 static PyObject*
autohintmm(PyObject * self,PyObject * args)243 autohintmm(PyObject* self, PyObject* args)
244 {
245     PyObject* inObj = NULL;
246     Py_ssize_t inCount = 0;
247     PyObject* mastersObj = NULL;
248     Py_ssize_t mastersCount = 0;
249     PyObject* outSeq = NULL;
250     const char** masters;
251     bool error = true;
252     Py_ssize_t i;
253 
254     if (!PyArg_ParseTuple(args, "O!O!", &PyTuple_Type, &inObj, &PyTuple_Type,
255                           &mastersObj))
256         return NULL;
257 
258     inCount = PyTuple_GET_SIZE(inObj);
259     mastersCount = PyTuple_GET_SIZE(mastersObj);
260     if (inCount != mastersCount) {
261         PyErr_SetString(
262           PyExc_TypeError,
263           "Length of \"glyphs\" must equal length of \"masters\".");
264         return NULL;
265     }
266 
267     if (inCount <= 1) {
268         PyErr_SetString(PyExc_TypeError, "Length of input glyphs must be > 1");
269         return NULL;
270     }
271 
272     masters = PyMem_RawCalloc(mastersCount, sizeof(char*));
273     if (!masters) {
274         PyErr_NoMemory();
275         return NULL;
276     }
277 
278     for (i = 0; i < mastersCount; i++) {
279         PyObject* obj = PyTuple_GET_ITEM(mastersObj, i);
280         masters[i] = PyBytes_AsString(obj);
281         if (!masters[i])
282             goto done;
283     }
284 
285     AC_SetMemManager(NULL, memoryManager);
286     AC_SetReportCB(reportCB);
287 
288     outSeq = PyTuple_New(inCount);
289     if (outSeq) {
290         int result = -1;
291 
292         const char** inGlyphs = PyMem_RawCalloc(inCount, sizeof(char*));
293         ACBuffer** outGlyphs = PyMem_RawCalloc(inCount, sizeof(ACBuffer*));
294         if (!inGlyphs || !outGlyphs) {
295             PyErr_NoMemory();
296             goto finish;
297         }
298 
299         for (i = 0; i < inCount; i++) {
300             PyObject* glyphObj = PyTuple_GET_ITEM(inObj, i);
301             inGlyphs[i] = PyBytes_AsString(glyphObj);
302             if (!inGlyphs[i])
303                 goto finish;
304             outGlyphs[i] = ACBufferNew(4 * strlen(inGlyphs[i]));
305         }
306 
307         result = AutoHintStringMM(inGlyphs, mastersCount, masters, outGlyphs);
308         if (result == AC_Success) {
309             error = false;
310             for (i = 0; i < inCount; i++) {
311                 PyObject* outObj;
312                 char* data;
313                 size_t len;
314 
315                 ACBufferRead(outGlyphs[i], &data, &len);
316                 outObj = PyBytes_FromStringAndSize(data, len);
317                 PyTuple_SET_ITEM(outSeq, i, outObj);
318             }
319         }
320 
321     finish:
322         if (outGlyphs) {
323             for (i = 0; i < inCount; i++) {
324                 ACBufferFree(outGlyphs[i]);
325                 outGlyphs[i] = NULL;
326             }
327         }
328 
329         PyMem_RawFree(inGlyphs);
330         PyMem_RawFree(outGlyphs);
331 
332         if (result != AC_Success) {
333             switch (result) {
334                 case -1:
335                     /* Do nothing, we already called PyErr_* */
336                     break;
337                 case AC_FatalError:
338                     PyErr_SetString(PsAutoHintError, "Fatal error");
339                     break;
340                 case AC_InvalidParameterError:
341                     PyErr_SetString(PyExc_ValueError, "Invalid glyph data");
342                     break;
343                 case AC_UnknownError:
344                 default:
345                     PyErr_SetString(PsAutoHintError, "Hinting failed");
346                     break;
347             }
348         }
349     }
350 
351 done:
352     PyMem_RawFree(masters);
353 
354     if (error) {
355         Py_XDECREF(outSeq);
356         return NULL;
357     }
358 
359     return outSeq;
360 }
361 
362 /* clang-format off */
363 static PyMethodDef psautohint_methods[] = {
364   { "autohint", autohint, METH_VARARGS, autohint_doc },
365   { "autohintmm", autohintmm, METH_VARARGS, autohintmm_doc },
366   { NULL, NULL, 0, NULL }
367 };
368 /* clang-format on */
369 
370 static char psautohint_doc[] =
371   "Python wrapper for Adobe's PostScrupt autohinter.\n"
372   "\n"
373   "autohint() -- Autohint glyphs.\n";
374 
375 #define SETUPMODULE                                                            \
376     PyModule_AddStringConstant(m, "version", AC_getVersion());                 \
377     PsAutoHintError = PyErr_NewException("psautohint.error", NULL, NULL);      \
378     Py_INCREF(PsAutoHintError);                                                \
379     PyModule_AddObject(m, "error", PsAutoHintError);
380 
381 /* clang-format off */
382 static struct PyModuleDef psautohint_module = {
383   PyModuleDef_HEAD_INIT,
384   "_psautohint",
385   psautohint_doc,
386   0,
387   psautohint_methods,
388   NULL,
389   NULL,
390   NULL,
391   NULL
392 };
393 /* clang-format on */
394 
395 PyMODINIT_FUNC
PyInit__psautohint(void)396 PyInit__psautohint(void)
397 {
398     PyObject* m;
399 
400     m = PyModule_Create(&psautohint_module);
401     if (m == NULL)
402         return NULL;
403 
404     SETUPMODULE
405 
406     return m;
407 }
408