1 #define UNICODE
2 #define PY_SSIZE_T_CLEAN
3 
4 #include <Python.h>
5 #include <datetime.h>
6 #include <errno.h>
7 
8 #include <stdlib.h>
9 #include <fcntl.h>
10 #include <stdio.h>
11 #define _USE_MATH_DEFINES
12 #include <math.h>
13 #include <string.h>
14 
15 #define MIN(x, y) ((x < y) ? x : y)
16 #define MAX(x, y) ((x > y) ? x : y)
17 #define CLAMP(value, lower, upper) ((value > upper) ? upper : ((value < lower) ? lower : value))
18 #define STRIDE(width, r, c) ((width * (r)) + (c))
19 
20 #ifdef _MSC_VER
21 #ifndef uint32_t
22 typedef unsigned __int32 uint32_t;
23 #endif
24 
25 #ifndef uint8_t
26 typedef unsigned __int8 uint8_t;
27 #endif
28 #else
29 #include <stdint.h>
30 #endif
31 
32 static PyObject *
speedup_parse_date(PyObject * self,PyObject * args)33 speedup_parse_date(PyObject *self, PyObject *args) {
34     const char *raw, *orig, *tz;
35     char *end;
36     long year, month, day, hour, minute, second, tzh = 0, tzm = 0, sign = 0;
37     size_t len;
38     if(!PyArg_ParseTuple(args, "s", &raw)) return NULL;
39     while ((*raw == ' ' || *raw == '\t' || *raw == '\n' || *raw == '\r' || *raw == '\f' || *raw == '\v') && *raw != 0) raw++;
40     len = strlen(raw);
41     if (len < 19) Py_RETURN_NONE;
42 
43     orig = raw;
44 
45     year = strtol(raw, &end, 10);
46     if ((end - raw) != 4) Py_RETURN_NONE;
47     raw += 5;
48 
49 
50     month = strtol(raw, &end, 10);
51     if ((end - raw) != 2) Py_RETURN_NONE;
52     raw += 3;
53 
54     day = strtol(raw, &end, 10);
55     if ((end - raw) != 2) Py_RETURN_NONE;
56     raw += 3;
57 
58     hour = strtol(raw, &end, 10);
59     if ((end - raw) != 2) Py_RETURN_NONE;
60     raw += 3;
61 
62     minute = strtol(raw, &end, 10);
63     if ((end - raw) != 2) Py_RETURN_NONE;
64     raw += 3;
65 
66     second = strtol(raw, &end, 10);
67     if ((end - raw) != 2) Py_RETURN_NONE;
68 
69     tz = orig + len - 6;
70 
71     if (*tz == '+') sign = +1;
72     if (*tz == '-') sign = -1;
73     if (sign != 0) {
74         // We have TZ info
75         tz += 1;
76 
77         tzh = strtol(tz, &end, 10);
78         if ((end - tz) != 2) Py_RETURN_NONE;
79         tz += 3;
80 
81         tzm = strtol(tz, &end, 10);
82         if ((end - tz) != 2) Py_RETURN_NONE;
83     }
84 
85     return Py_BuildValue("lllllll", year, month, day, hour, minute, second,
86             (tzh*60 + tzm)*sign*60);
87 }
88 
89 
90 static PyObject*
speedup_pdf_float(PyObject * self,PyObject * args)91 speedup_pdf_float(PyObject *self, PyObject *args) {
92     double f = 0.0, a = 0.0;
93     char *buf = "0", *dot;
94     void *free_buf = NULL;
95     int precision = 6, l = 0;
96     PyObject *ret;
97 
98     if(!PyArg_ParseTuple(args, "d", &f)) return NULL;
99 
100     a = fabs(f);
101 
102     if (a > 1.0e-7) {
103         if(a > 1) precision = MIN(MAX(0, 6-(int)log10(a)), 6);
104         buf = PyOS_double_to_string(f, 'f', precision, 0, NULL);
105         if (buf != NULL) {
106             free_buf = (void*)buf;
107             if (precision > 0) {
108                 l = (int)(strlen(buf) - 1);
109                 while (l > 0 && buf[l] == '0') l--;
110                 if (buf[l] == ',' || buf[l] == '.') buf[l] = 0;
111                 else buf[l+1] = 0;
112                 if ( (dot = strchr(buf, ',')) ) *dot = '.';
113             }
114         } else if (!PyErr_Occurred()) PyErr_SetString(PyExc_TypeError, "Float->str failed.");
115     }
116 
117     ret = PyUnicode_FromString(buf);
118     if (free_buf != NULL) PyMem_Free(free_buf);
119     return ret;
120 }
121 
122 static PyObject*
speedup_detach(PyObject * self,PyObject * args)123 speedup_detach(PyObject *self, PyObject *args) {
124     char *devnull = NULL;
125     if (!PyArg_ParseTuple(args, "s", &devnull)) return NULL;
126     if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrnoWithFilename(PyExc_OSError, devnull);
127     if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrnoWithFilename(PyExc_OSError, devnull);
128     if (freopen(devnull, "w", stderr) == NULL)  return PyErr_SetFromErrnoWithFilename(PyExc_OSError, devnull);
129     Py_RETURN_NONE;
130 }
131 
calculate_gaussian_kernel(Py_ssize_t size,double * kernel,double radius)132 static void calculate_gaussian_kernel(Py_ssize_t size, double *kernel, double radius) {
133     const double sqr = radius * radius;
134     const double factor = 1.0 / (2 * M_PI * sqr);
135     const double denom = 2 * sqr;
136     double *t, sum = 0;
137     Py_ssize_t r, c, center = size / 2;
138 
139     for (r = 0; r < size; r++) {
140         t = kernel + (r * size);
141         for (c = 0; c < size; c++) {
142             t[c] = factor * pow(M_E, - ( ( (r - center) * (r - center) + (c - center) * (c - center) ) / denom ));
143         }
144     }
145 
146     // Normalize matrix
147     for (r = 0; r < size * size; r++) sum += kernel[r];
148     sum = 1 / sum;
149     for (r = 0; r < size * size; r++) kernel[r] *= sum;
150 }
151 
152 static PyObject*
speedup_create_texture(PyObject * self,PyObject * args,PyObject * kw)153 speedup_create_texture(PyObject *self, PyObject *args, PyObject *kw) {
154     PyObject *ret = NULL;
155     Py_ssize_t width, height, weight = 3, i, j, r, c, half_weight;
156     double pixel, *mask = NULL, radius = 1, *kernel = NULL, blend_alpha = 0.1;
157     float density = 0.7f;
158     unsigned char base_r, base_g, base_b, blend_r = 0, blend_g = 0, blend_b = 0, *ppm = NULL, *t = NULL;
159     char header[100] = {0};
160     static char* kwlist[] = {"blend_red", "blend_green", "blend_blue", "blend_alpha", "density", "weight", "radius", NULL};
161 
162     if (!PyArg_ParseTupleAndKeywords(args, kw, "nnbbb|bbbdfnd", kwlist, &width, &height, &base_r, &base_g, &base_b, &blend_r, &blend_g, &blend_b, &blend_alpha, &density, &weight, &radius)) return NULL;
163     if (weight % 2 != 1 || weight < 1) { PyErr_SetString(PyExc_ValueError, "The weight must be an odd positive number"); return NULL; }
164     if (radius <= 0) { PyErr_SetString(PyExc_ValueError, "The radius must be positive"); return NULL; }
165     if (width > 100000 || height > 10000) { PyErr_SetString(PyExc_ValueError, "The width or height is too large"); return NULL; }
166     if (width < 1 || height < 1) { PyErr_SetString(PyExc_ValueError, "The width or height is too small"); return NULL; }
167     snprintf(header, 99, "P6\n%d %d\n255\n", (int)width, (int)height);
168 
169     kernel = (double*)calloc(weight * weight, sizeof(double));
170     if (kernel == NULL) { PyErr_NoMemory(); return NULL; }
171     mask = (double*)calloc(width * height, sizeof(double));
172     if (mask == NULL) { free(kernel); PyErr_NoMemory(); return NULL;}
173     ppm = (unsigned char*)calloc(strlen(header) + (3 * width * height), sizeof(unsigned char));
174     if (ppm == NULL) { free(kernel); free(mask); PyErr_NoMemory(); return NULL; }
175 
176     calculate_gaussian_kernel(weight, kernel, radius);
177 
178     // Random noise, noisy pixels are blend_alpha, other pixels are 0
179     for (i = 0; i < width * height; i++) {
180         if (((float)(rand()) / RAND_MAX) <= density) mask[i] = blend_alpha;
181     }
182 
183     // Blur the noise using the gaussian kernel
184     half_weight = weight / 2;
185     for (r = 0; r < height; r++) {
186         for (c = 0; c < width; c++) {
187             pixel = 0;
188             for (i = -half_weight; i <= half_weight; i++) {
189                 for (j = -half_weight; j <= half_weight; j++) {
190                     pixel += (*(mask + STRIDE(width, CLAMP(r + i, 0, height - 1), CLAMP(c + j, 0, width - 1)))) * (*(kernel + STRIDE(weight, half_weight + i, half_weight + j)));
191                 }
192             }
193             *(mask + STRIDE(width, r, c)) = CLAMP(pixel, 0, 1);
194         }
195     }
196 
197     // Create the texture in PPM (P6) format
198     memcpy(ppm, header, strlen(header));
199     t = ppm + strlen(header);
200     for (i = 0, j = 0; j < width * height; i += 3, j += 1) {
201 #define BLEND(src, dest) ( ((unsigned char)(src * mask[j])) + ((unsigned char)(dest * (1 - mask[j]))) )
202         t[i] = BLEND(blend_r, base_r);
203         t[i+1] = BLEND(blend_g, base_g);
204         t[i+2] = BLEND(blend_b, base_b);
205     }
206 
207     ret = Py_BuildValue("s", ppm);
208     free(mask); mask = NULL;
209     free(kernel); kernel = NULL;
210     free(ppm); ppm = NULL;
211     return ret;
212 }
213 
214 static PyObject*
speedup_websocket_mask(PyObject * self,PyObject * args)215 speedup_websocket_mask(PyObject *self, PyObject *args) {
216 	PyObject *data = NULL, *mask = NULL;
217 	Py_buffer data_buf = {0}, mask_buf = {0};
218 	Py_ssize_t offset = 0, i = 0;
219 	char *dbuf = NULL, *mbuf = NULL;
220     int ok = 0;
221 
222     if(!PyArg_ParseTuple(args, "OO|n", &data, &mask, &offset)) return NULL;
223 
224 	if (PyObject_GetBuffer(data, &data_buf, PyBUF_SIMPLE|PyBUF_WRITABLE) != 0) return NULL;
225 	if (PyObject_GetBuffer(mask, &mask_buf, PyBUF_SIMPLE) != 0) goto done;
226 
227 	dbuf = (char*)data_buf.buf; mbuf = (char*)mask_buf.buf;
228 	for(i = 0; i < data_buf.len; i++) dbuf[i] ^= mbuf[(i + offset) & 3];
229     ok = 1;
230 
231 done:
232     if(data_buf.obj) PyBuffer_Release(&data_buf);
233     if(mask_buf.obj) PyBuffer_Release(&mask_buf);
234     if (ok) { Py_RETURN_NONE; }
235     return NULL;
236 }
237 
238 #define UTF8_ACCEPT 0
239 #define UTF8_REJECT 1
240 
241 static const uint8_t utf8d[] = {
242   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
243   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
244   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
245   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
246   1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
247   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
248   8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
249   0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
250   0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
251   0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
252   1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
253   1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
254   1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
255   1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
256 };
257 
258 #ifdef _MSC_VER
259 static void __inline
260 #else
261 static void inline
262 #endif
utf8_decode_(uint32_t * state,uint32_t * codep,uint8_t byte)263 utf8_decode_(uint32_t* state, uint32_t* codep, uint8_t byte) {
264   /* Comes from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
265    * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
266    * Used under license: https://opensource.org/licenses/MIT
267    */
268   uint32_t type = utf8d[byte];
269 
270   *codep = (*state != UTF8_ACCEPT) ?
271     (byte & 0x3fu) | (*codep << 6) :
272     (0xff >> type) & (byte);
273 
274   *state = utf8d[256 + *state*16 + type];
275 }
276 
277 static PyObject*
utf8_decode(PyObject * self,PyObject * args)278 utf8_decode(PyObject *self, PyObject *args) {
279 	uint8_t *dbuf = NULL;
280 	uint32_t state = UTF8_ACCEPT, codep = 0, *buf = NULL;
281 	PyObject *data_obj = NULL, *ans = NULL;
282 	Py_buffer pbuf;
283 	Py_ssize_t i = 0, pos = 0;
284 
285     if(!PyArg_ParseTuple(args, "O|II", &data_obj, &state, &codep)) return NULL;
286 	if (PyObject_GetBuffer(data_obj, &pbuf, PyBUF_SIMPLE) != 0) return NULL;
287 	buf = (uint32_t*)PyMem_Malloc(sizeof(uint32_t) * pbuf.len);
288 	if (buf == NULL) goto error;
289 	dbuf = (uint8_t*)pbuf.buf;
290 
291 	for (i = 0; i < pbuf.len; i++) {
292 		utf8_decode_(&state, &codep, dbuf[i]);
293 		if (state == UTF8_ACCEPT) buf[pos++] = codep;
294 		else if (state == UTF8_REJECT) { PyErr_SetString(PyExc_ValueError, "Invalid byte in UTF-8 string"); goto error; }
295 	}
296 	ans = PyUnicode_DecodeUTF32((const char*)buf, pos * sizeof(uint32_t), "strict", NULL);
297 error:
298     if (pbuf.obj) PyBuffer_Release(&pbuf);
299 	if (buf) { PyMem_Free(buf); buf = NULL; }
300 	if (ans == NULL) return ans;
301 	return Py_BuildValue("NII", ans, state, codep);
302 }
303 
304 static PyObject*
clean_xml_chars(PyObject * self,PyObject * text)305 clean_xml_chars(PyObject *self, PyObject *text) {
306     PyObject *result = NULL;
307     void *result_text = NULL;
308     Py_ssize_t src_i, target_i;
309     enum PyUnicode_Kind text_kind;
310     Py_UCS4 ch;
311 
312     if (!PyUnicode_Check(text)) {
313         PyErr_SetString(PyExc_TypeError, "A unicode string is required");
314         return NULL;
315     }
316     if(PyUnicode_READY(text) != 0) {
317         // just return null, an exception is already set by READY()
318         return NULL;
319     }
320     if(PyUnicode_GET_LENGTH(text) == 0) {
321         // make sure that malloc(0) will never happen
322         return text;
323     }
324 
325     text_kind = PyUnicode_KIND(text);
326     // Once we've called READY(), our string is in canonical form, which means
327     // it is encoded using UTF-{8,16,32}, such that each codepoint is one
328     // element in the array. The value of the Kind enum is the size of each
329     // character.
330     result_text = malloc(PyUnicode_GET_LENGTH(text) * text_kind);
331     if (result_text == NULL) return PyErr_NoMemory();
332 
333     target_i = 0;
334     for (src_i = 0; src_i < PyUnicode_GET_LENGTH(text); src_i++) {
335         ch = PyUnicode_READ(text_kind, PyUnicode_DATA(text), src_i);
336         // based on https://en.wikipedia.org/wiki/Valid_characters_in_XML#Non-restricted_characters
337         // python 3.3+ unicode strings never contain surrogate pairs, since if
338         // they did, they would be represented as UTF-32
339         if ((0x20 <= ch && ch <= 0x7e) ||
340                 ch == 0x9 || ch == 0xa || ch == 0xd || ch == 0x85 ||
341 				(0x00A0 <= ch && ch <= 0xD7FF) ||
342 				(0xE000 <= ch && ch <= 0xFDCF) ||
343 				(0xFDF0 <= ch && ch <= 0xFFFD) ||
344                 (0xffff < ch && ch <= 0x10ffff)) {
345             PyUnicode_WRITE(text_kind, result_text, target_i, ch);
346             target_i += 1;
347         }
348     }
349 
350     // using text_kind here is ok because we don't create any characters that
351     // are larger than might already exist
352     result = PyUnicode_FromKindAndData(text_kind, result_text, target_i);
353     free(result_text);
354     return result;
355 }
356 
357 static PyObject *
speedup_iso_8601(PyObject * self,PyObject * args)358 speedup_iso_8601(PyObject *self, PyObject *args) {
359     char *str = NULL, *c = NULL;
360     int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, usecond = 0, i = 0, tzhour = 1000, tzminute = 0, tzsign = 0;
361 
362     if (!PyArg_ParseTuple(args, "s", &str)) return NULL;
363     c = str;
364 
365 #define RAISE(msg) return PyErr_Format(PyExc_ValueError, "%s is not a valid ISO 8601 datestring: %s", str, msg);
366 #define CHAR_IS_DIGIT(c) (*c >= '0' && *c <= '9')
367 #define READ_DECIMAL_NUMBER(max_digits, x, abort) \
368     for (i = 0; i < max_digits; i++) { \
369         if (CHAR_IS_DIGIT(c)) x = 10 * x + *c++ - '0'; \
370         else { abort; } \
371     }
372 #define OPTIONAL_SEPARATOR(x) if(*c == x) c++;
373 
374     // Ignore leading whitespace
375     while(*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t' || *c == '\v' || *c == '\f') c++;
376 
377     // Year
378     READ_DECIMAL_NUMBER(4, year, RAISE("No year specified"));
379     OPTIONAL_SEPARATOR('-');
380     // Month (optional)
381     READ_DECIMAL_NUMBER(2, month, break);
382     if (month == 0) month = 1; // YYYY format
383     else {
384         OPTIONAL_SEPARATOR('-');
385 
386         // Day (optional)
387         READ_DECIMAL_NUMBER(2, day, break);
388     }
389     if (day == 0) day = 1; // YYYY-MM format
390     if (month > 12) RAISE("month greater than 12");
391 
392     if (*c == 'T' || *c == ' ') // Time separator
393     {
394         c++;
395 
396         // Hour
397         READ_DECIMAL_NUMBER(2, hour, RAISE("No hour specified"));
398         OPTIONAL_SEPARATOR(':');
399         // Minute (optional)
400         READ_DECIMAL_NUMBER(2, minute, break);
401         OPTIONAL_SEPARATOR(':');
402         // Second (optional)
403         READ_DECIMAL_NUMBER(2, second, break);
404 
405         if (*c == '.' || *c == ',') // separator for microseconds
406         {
407             c++;
408             // Parse fraction of second up to 6 places
409             READ_DECIMAL_NUMBER(6, usecond, break);
410             // Omit excessive digits
411             while (CHAR_IS_DIGIT(c)) c++;
412             // If we break early, fully expand the usecond
413             while (i++ < 6) usecond *= 10;
414         }
415     }
416 
417     switch(*c) {
418         case 'Z':
419             tzhour = 0; c++; break;
420         case '+':
421             tzsign = 1; c++; break;
422         case '-':
423             tzsign = -1; c++; break;
424         default:
425             break;
426     }
427 
428     if (tzsign != 0) {
429         tzhour = 0;
430         READ_DECIMAL_NUMBER(2, tzhour, break);
431         OPTIONAL_SEPARATOR(':');
432         READ_DECIMAL_NUMBER(2, tzminute, break);
433     }
434 
435     return Py_BuildValue("NOi", PyDateTime_FromDateAndTime(year, month, day, hour, minute, second, usecond), (tzhour == 1000) ? Py_False : Py_True, tzsign*60*(tzhour*60 + tzminute));
436 }
437 
438 #ifndef _MSC_VER
439 #include <pthread.h>
440 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
441 #define FREEBSD_SET_NAME
442 #endif
443 #if defined(__APPLE__)
444 // I can't figure out how to get pthread.h to include this definition on macOS. MACOSX_DEPLOYMENT_TARGET does not work.
445 extern int pthread_setname_np(const char *name);
446 #elif defined(FREEBSD_SET_NAME)
447 // Function has a different name on FreeBSD
448 void pthread_set_name_np(pthread_t tid, const char *name);
449 #elif defined(__NetBSD__)
450 // pthread.h provides the symbol
451 #elif defined(__HAIKU__)
452 // Haiku doesn't support pthread_set_name_np yet
453 #else
454 // Need _GNU_SOURCE for pthread_setname_np on linux and that causes other issues on systems with old glibc
455 extern int pthread_setname_np(pthread_t, const char *name);
456 #endif
457 #endif
458 
459 
460 static PyObject*
set_thread_name(PyObject * self,PyObject * args)461 set_thread_name(PyObject *self, PyObject *args) {
462 	(void)(self); (void)(args);
463 #if defined(_MSC_VER) || defined(__HAIKU__)
464 	PyErr_SetString(PyExc_RuntimeError, "Setting thread names not supported on on this platform");
465 	return NULL;
466 #else
467 	char *name;
468 	int ret;
469 	if (!PyArg_ParseTuple(args, "s", &name)) return NULL;
470 	while (1) {
471 		errno = 0;
472 #if defined(__APPLE__)
473 		ret = pthread_setname_np(name);
474 #elif defined(FREEBSD_SET_NAME)
475 		pthread_set_name_np(pthread_self(), name);
476 		ret = 0;
477 #elif defined(__NetBSD__)
478 		ret = pthread_setname_np(pthread_self(), "%s", name);
479 #else
480 		ret = pthread_setname_np(pthread_self(), name);
481 #endif
482 		if (ret != 0 && (errno == EINTR || errno == EAGAIN)) continue;
483 		break;
484 	}
485     if (ret != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
486 	Py_RETURN_NONE;
487 #endif
488 }
489 
490 #define char_is_ignored(ch) (ch <= 32)
491 
492 static size_t
count_chars_in(PyObject * text)493 count_chars_in(PyObject *text) {
494 	size_t ans = 0;
495 	if (PyUnicode_READY(text) != 0) return 0;
496 	int kind = PyUnicode_KIND(text);
497 	void *data = PyUnicode_DATA(text);
498 	Py_ssize_t len = PyUnicode_GET_LENGTH(text);
499 	ans = len;
500 	for (Py_ssize_t i = 0; i < len; i++) {
501 		if (char_is_ignored(PyUnicode_READ(kind, data, i))) ans--;
502 	}
503 	return ans;
504 }
505 
506 static PyObject*
get_element_char_length(PyObject * self,PyObject * args)507 get_element_char_length(PyObject *self, PyObject *args) {
508 	(void)(self);
509 	const char *tag_name;
510 	PyObject *text, *tail;
511 	if (!PyArg_ParseTuple(args, "sOO", &tag_name, &text, &tail)) return NULL;
512 	const char *b = strrchr(tag_name, '}');
513 	if (b) tag_name = b + 1;
514 	char ltagname[16];
515 	const size_t tag_name_len = strnlen(tag_name, sizeof(ltagname)-1);
516 	for (size_t i = 0; i < tag_name_len; i++) {
517 		if ('A' <= tag_name[i] && tag_name[i] <= 'Z') ltagname[i] = 32 + tag_name[i];
518 		else ltagname[i] = tag_name[i];
519 	}
520 	int is_ignored_tag = 0;
521 	size_t ans = 0;
522 #define EQ(x) memcmp(ltagname, #x, sizeof(#x) - 1) == 0
523 	if (EQ(script) || EQ(noscript) || EQ(style) || EQ(title)) is_ignored_tag = 1;
524 	if (EQ(img) || EQ(svg)) ans += 1000;
525 #undef EQ
526 	if (tail != Py_None) ans += count_chars_in(tail);
527 	if (text != Py_None && !is_ignored_tag) ans += count_chars_in(text);
528 	return PyLong_FromSize_t(ans);
529 }
530 
531 
532 static PyMethodDef speedup_methods[] = {
533     {"parse_date", speedup_parse_date, METH_VARARGS,
534         "parse_date()\n\nParse ISO dates faster (specialized for dates stored in the calibre db)."
535     },
536 
537     {"parse_iso8601", speedup_iso_8601, METH_VARARGS,
538         "parse_iso8601(datestring)\n\nParse ISO 8601 dates faster. More spec compliant than parse_date()"
539     },
540 
541     {"pdf_float", speedup_pdf_float, METH_VARARGS,
542         "pdf_float()\n\nConvert float to a string representation suitable for PDF"
543     },
544 
545     {"detach", speedup_detach, METH_VARARGS,
546         "detach()\n\nRedirect the standard I/O stream to the specified file (usually os.devnull)"
547     },
548 
549     {"create_texture", (PyCFunction)speedup_create_texture, METH_VARARGS | METH_KEYWORDS,
550         "create_texture(width, height, red, green, blue, blend_red=0, blend_green=0, blend_blue=0, blend_alpha=0.1, density=0.7, weight=3, radius=1)\n\n"
551             "Create a texture of the specified width and height from the specified color."
552             " The texture is created by blending in random noise of the specified blend color into a flat image."
553             " All colors are numbers between 0 and 255. 0 <= blend_alpha <= 1 with 0 being fully transparent."
554             " 0 <= density <= 1 is used to control the amount of noise in the texture."
555             " weight and radius control the Gaussian convolution used for blurring of the noise. weight must be an odd positive integer. Increasing the weight will tend to blur out the noise. Decreasing it will make it sharper."
556             " This function returns an image (bytestring) in the PPM format as the texture."
557     },
558 
559     {"websocket_mask", speedup_websocket_mask, METH_VARARGS,
560         "websocket_mask(data, mask [, offset=0)\n\nXOR the data (bytestring) with the specified (must be 4-byte bytestring) mask"
561     },
562 
563 	{"utf8_decode", utf8_decode, METH_VARARGS,
564 		"utf8_decode(data, [, state=0, codep=0)\n\nDecode an UTF-8 bytestring, using a strict UTF-8 decoder, that unlike python does not allow orphaned surrogates. Returns a unicode object and the state."
565 	},
566 
567     {"clean_xml_chars", clean_xml_chars, METH_O,
568         "clean_xml_chars(unicode_object)\n\nRemove codepoints in unicode_object that are not allowed in XML"
569     },
570 
571 	{"set_thread_name", set_thread_name, METH_VARARGS,
572 		"set_thread_name(name)\n\nWrapper for pthread_setname_np"
573 	},
574 
575 	{"get_element_char_length", get_element_char_length, METH_VARARGS,
576 		"get_element_char_length(tag_name, text, tail)\n\nGet the number of chars in specified tag"
577 	},
578 
579     {NULL, NULL, 0, NULL}
580 };
581 
582 static int
exec_module(PyObject * module)583 exec_module(PyObject *module) {
584     PyDateTime_IMPORT;
585 #ifndef _WIN32
586     PyModule_AddIntConstant(module, "O_CLOEXEC", O_CLOEXEC);
587 #endif
588     return 0;
589 }
590 
591 static PyModuleDef_Slot slots[] = { {Py_mod_exec, exec_module}, {0, NULL} };
592 
593 static struct PyModuleDef module_def = {
594     .m_base     = PyModuleDef_HEAD_INIT,
595     .m_name     = "speedup",
596     .m_doc      = "Implementation of methods in C for speed.",
597     .m_methods  = speedup_methods,
598     .m_slots    = slots,
599 };
600 
PyInit_speedup(void)601 CALIBRE_MODINIT_FUNC PyInit_speedup(void) { return PyModuleDef_Init(&module_def); }
602