• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 22260341992

21 Feb 2026 04:37PM UTC coverage: 83.606% (+0.2%) from 83.377%
22260341992

Pull #219

github

web-flow
Merge 8e40641da into 0f4b52676
Pull Request #219: loader: introduce component adapter and route backend calls

26143 of 50418 branches covered (51.85%)

169 of 185 new or added lines in 3 files covered. (91.35%)

5 existing lines in 2 files now uncovered.

46378 of 55472 relevant lines covered (83.61%)

4076346.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.16
/src/loader.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2021-2025 libsixel developers. See `AUTHORS`.
5
 * Copyright (c) 2014-2019 Hayaki Saito
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25

26
#if defined(HAVE_CONFIG_H)
27
#include "config.h"
28
#endif
29
#if !defined(_POSIX_C_SOURCE)
30
# define _POSIX_C_SOURCE 200809L
31
#endif
32

33
/* STDC_HEADERS */
34
#include <stdio.h>
35
#include <stdlib.h>
36

37
#if HAVE_STRING_H
38
# include <string.h>
39
#endif
40
#if HAVE_CTYPE_H
41
# include <ctype.h>
42
#endif
43
#if HAVE_STDARG_H
44
# include <stdarg.h>
45
#endif
46
#if HAVE_SYS_TYPES_H
47
# include <sys/types.h>
48
#endif
49
#if HAVE_UNISTD_H
50
# include <unistd.h>
51
#endif
52
#if !defined(PATH_MAX)
53
#define PATH_MAX 4096
54
#endif
55
#if HAVE_FCNTL_H
56
# include <fcntl.h>
57
#endif
58
#if HAVE_SYS_TIME_H
59
# include <sys/time.h>
60
#elif HAVE_TIME_H
61
# include <time.h>
62
#endif  /* HAVE_SYS_TIME_H HAVE_TIME_H */
63
#if defined(_WIN32)
64
# if !defined(UNICODE)
65
#  define UNICODE
66
# endif
67
# if !defined(_UNICODE)
68
#  define _UNICODE
69
# endif
70
# if !defined(WIN32_LEAN_AND_MEAN)
71
#  define WIN32_LEAN_AND_MEAN
72
# endif
73
# include <windows.h>
74
#endif
75
#if HAVE_ERRNO_H
76
# include <errno.h>
77
#endif
78
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
79
# include <CoreServices/CoreServices.h>
80
#endif
81
#if HAVE_SYS_WAIT_H
82
# include <sys/wait.h>
83
#endif
84
#if HAVE_SPAWN_H
85
# include <spawn.h>
86
#endif
87
#if HAVE_LIMITS_H
88
# include <limits.h>
89
#endif
90
#if HAVE_DIRENT_H
91
# include <dirent.h>
92
#endif
93

94
#include <sixel.h>
95
#include "loader.h"
96
#include "loader-builtin.h"
97
#include "loader-common.h"
98
#include "loader-coregraphics.h"
99
#include "loader-gd.h"
100
#include "loader-gdk-pixbuf2.h"
101
#include "loader-gnome-thumbnailer.h"
102
#include "loader-libjpeg.h"
103
#include "loader-libpng.h"
104
#include "loader-librsvg.h"
105
#include "loader-quicklook.h"
106
#include "loader-registry.h"
107
#include "loader-component.h"
108
#include "loader-component-legacy.h"
109
#include "loader-wic.h"
110
#include "compat_stub.h"
111
#include "frame.h"
112
#include "chunk.h"
113
#include "allocator.h"
114
#include "encoder.h"
115
#include "logger.h"
116
#include "options.h"
117
#include "sixel_atomic.h"
118

119
/*
120
 * Internal loader state carried across backends.  The fields mirror the
121
 * original `loader.c` layout to keep statistics, logging, and allocator
122
 * ownership centralized while implementations move into per-backend files.
123
 */
124
struct sixel_loader {
125
    sixel_atomic_u32_t ref;
126
    int fstatic;
127
    int fuse_palette;
128
    int reqcolors;
129
    unsigned char bgcolor[3];
130
    int has_bgcolor;
131
    int loop_control;
132
    int finsecure;
133
    int has_start_frame_no;
134
    int start_frame_no;
135
    int const *cancel_flag;
136
    void *context;
137
    sixel_logger_t logger;
138
    char *loader_order;
139
    sixel_allocator_t *allocator;
140
    char last_loader_name[64];
141
    char last_source_path[PATH_MAX];
142
    size_t last_input_bytes;
143
    int callback_failed;
144
    int log_loader_finished;
145
    char log_path[PATH_MAX];
146
    char log_loader_name[64];
147
    size_t log_input_bytes;
148
};
149

150
typedef struct sixel_loader_callback_state {
151
    sixel_loader_t *loader;
152
    sixel_load_image_function fn;
153
    void *context;
154
} sixel_loader_callback_state_t;
155

156
int
157
sixel_loader_callback_is_canceled(void *data)
38,579✔
158
{
159
    sixel_loader_callback_state_t *state;
22,776✔
160

161
    state = (sixel_loader_callback_state_t *)data;
38,579✔
162
    if (state == NULL || state->loader == NULL ||
38,579!
163
        state->loader->cancel_flag == NULL) {
38,484✔
164
        return 0;
9,008✔
165
    }
166

167
    return *state->loader->cancel_flag != 0;
24,407✔
168
}
19,036✔
169

170

171
#if HAVE_POSIX_SPAWNP
172
extern char **environ;
173
#endif
174

175
static char *
176
loader_strdup(char const *text, sixel_allocator_t *allocator)
1,782✔
177
{
178
    char *copy;
1,060✔
179
    size_t length;
1,060✔
180

181
    if (text == NULL) {
1,782!
182
        return NULL;
183
    }
184

185
    length = strlen(text) + 1;
1,782✔
186
    copy = (char *)sixel_allocator_malloc(allocator, length);
1,782✔
187
    if (copy == NULL) {
1,782!
188
        return NULL;
189
    }
190

191
    /* Copy the terminating NUL byte as part of length. */
192
    memcpy(copy, text, length);
1,782✔
193

194
    return copy;
1,782✔
195
}
1,020✔
196

197

198

199
/*
200
 * Emit loader stage markers.
201
 *
202
 * Loader callbacks run the downstream pipeline synchronously, so the finish
203
 * marker must be issued before invoking fn_load() to avoid inflating the
204
 * loader span. The helper keeps the formatting consistent with
205
 * sixel_encoder_log_stage() without depending on encoder internals.
206
 */
207
static void
208
loader_log_stage(sixel_loader_t *loader,
27,889✔
209
                 char const *event,
210
                 char const *fmt,
211
                 ...)
212
{
213
    sixel_logger_t *logger;
16,409✔
214
    char message[256];
16,409✔
215
    va_list args;
16,409✔
216

217
    logger = NULL;
27,889✔
218
    if (loader != NULL) {
27,889!
219
        logger = &loader->logger;
27,889✔
220
    }
13,641✔
221
    if (logger == NULL || logger->file == NULL || !logger->active) {
27,889!
222
        return;
27,889✔
223
    }
224

225
    message[0] = '\0';
×
226
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
227
# if defined(__clang__)
228
#  pragma clang diagnostic push
229
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
230
# elif defined(__GNUC__) && !defined(__PCC__)
231
#  pragma GCC diagnostic push
232
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
233
# endif
234
#endif
235
    va_start(args, fmt);
×
236
    if (fmt != NULL) {
×
237
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
238
# if defined(__clang__)
239
#  pragma clang diagnostic push
240
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
241
# elif defined(__GNUC__) && !defined(__PCC__)
242
#  pragma GCC diagnostic push
243
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
244
# endif
245
#endif
246
        (void)sixel_compat_vsnprintf(message, sizeof(message), fmt, args);
×
247
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
248
# if defined(__clang__)
249
#  pragma clang diagnostic pop
250
# elif defined(__GNUC__) && !defined(__PCC__)
251
#  pragma GCC diagnostic pop
252
# endif
253
#endif
254
    }
255
    va_end(args);
×
256
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
257
# if defined(__clang__)
258
#  pragma clang diagnostic pop
259
# elif defined(__GNUC__) && !defined(__PCC__)
260
#  pragma GCC diagnostic pop
261
# endif
262
#endif
263

264
    sixel_logger_logf(logger,
×
265
                      "worker",
266
                      "loader",
267
                      event,
268
                      -1,
269
                      -1,
270
                      0,
271
                      0,
272
                      0,
273
                      0,
274
                      "%s",
275
                      message);
276
}
13,641!
277

278
static SIXELSTATUS
279
loader_callback_trampoline(sixel_frame_t *frame, void *data)
14,956✔
280
{
281
    sixel_loader_callback_state_t *state;
8,805✔
282
    SIXELSTATUS status;
8,805✔
283
    sixel_loader_t *loader;
8,805✔
284

285
    state = (sixel_loader_callback_state_t *)data;
14,956✔
286
    loader = NULL;
14,956✔
287
    if (state == NULL || state->fn == NULL) {
14,956!
288
        return SIXEL_BAD_ARGUMENT;
289
    }
290

291
    loader = state->loader;
14,956✔
292
    if (loader != NULL && loader->log_loader_finished == 0) {
14,956!
293
        loader_log_stage(loader,
20,567✔
294
                         "finish",
295
                         "path=%s loader=%s bytes=%zu",
296
                         loader->log_path,
13,818✔
297
                         loader->log_loader_name,
13,818✔
298
                         loader->log_input_bytes);
6,749✔
299
        loader->log_loader_finished = 1;
13,818✔
300
    }
6,749✔
301

302
    status = state->fn(frame, state->context);
15,843✔
303
    if (SIXEL_FAILED(status) && state->loader != NULL) {
15,843!
304
        state->loader->callback_failed = 1;
×
305
    }
306

307
    return status;
9,958✔
308
}
7,387✔
309

310

311
static SIXELSTATUS
312
loader_apply_component_options(sixel_loader_component_t *component,
14,186✔
313
                               sixel_loader_t const *loader,
314
                               int reqcolors)
315
{
316
    SIXELSTATUS status;
8,338✔
317

318
    status = SIXEL_OK;
14,186✔
319
    if (component == NULL || loader == NULL) {
14,186!
320
        return SIXEL_BAD_ARGUMENT;
321
    }
322

323
    status = sixel_loader_component_setopt(component,
25,912✔
324
                                           SIXEL_LOADER_OPTION_REQUIRE_STATIC,
325
                                           &loader->fstatic);
14,186✔
326
    if (SIXEL_FAILED(status)) {
14,186!
327
        return status;
328
    }
329

330
    status = sixel_loader_component_setopt(component,
25,912✔
331
                                           SIXEL_LOADER_OPTION_USE_PALETTE,
332
                                           &loader->fuse_palette);
14,186✔
333
    if (SIXEL_FAILED(status)) {
14,186!
334
        return status;
335
    }
336

337
    status = sixel_loader_component_setopt(component,
14,186✔
338
                                           SIXEL_LOADER_OPTION_REQCOLORS,
339
                                           &reqcolors);
340
    if (SIXEL_FAILED(status)) {
14,186!
341
        return status;
342
    }
343

344
    status = sixel_loader_component_setopt(component,
21,154✔
345
                                           SIXEL_LOADER_OPTION_BGCOLOR,
346
                                           loader->has_bgcolor ?
14,186✔
347
                                               loader->bgcolor : NULL);
91✔
348
    if (SIXEL_FAILED(status)) {
14,186!
349
        return status;
350
    }
351

352
    status = sixel_loader_component_setopt(component,
25,912✔
353
                                           SIXEL_LOADER_OPTION_LOOP_CONTROL,
354
                                           &loader->loop_control);
14,186✔
355
    if (SIXEL_FAILED(status)) {
14,186!
356
        return status;
357
    }
358

359
    status = sixel_loader_component_setopt(
14,186✔
360
        component,
6,968✔
361
        SIXEL_LOADER_OPTION_START_FRAME_NO,
362
        loader->has_start_frame_no ? &loader->start_frame_no : NULL);
14,186✔
363
    if (SIXEL_FAILED(status)) {
14,186!
NEW
364
        return status;
×
365
    }
366

367
    return SIXEL_OK;
9,428✔
368
}
6,968✔
369

370
static int
371
loader_plan_contains(sixel_loader_entry_t const **plan,
31,561✔
372
                     size_t plan_length,
373
                     sixel_loader_entry_t const *entry)
374
{
375
    size_t index;
14,164✔
376

377
    for (index = 0; index < plan_length; ++index) {
94,083!
378
        if (plan[index] == entry) {
54,912!
379
            return 1;
380
        }
381
    }
49,855✔
382

383
    return 0;
31,561✔
384
}
26,959✔
385

386

387
static int
388
loader_token_matches(char const *token,
389
                     size_t token_length,
390
                     char const *name);
391

392
#if HAVE_WIC
393
static sixel_suboption_key_t const g_subkeys_loader_wic_loader[] = {
394
    {
395
        "ico_minsize",
396
        NULL,
397
        "SIXEL_LODER_WIC_ICO_MINSIZE",
398
        SIXEL_SUBOPTION_VALUE_FREE,
399
        NULL,
400
        0u
401
    }
402
};
403
#endif
404

405
static sixel_option_value_schema_t const g_schema_loader_values_loader[] = {
406
#if HAVE_LIBPNG
407
    { "libpng", 0, NULL, 0u },
408
#endif
409
#if HAVE_JPEG
410
    { "libjpeg", 0, NULL, 0u },
411
#endif
412
#if HAVE_WEBP
413
    { "libwebp", 0, NULL, 0u },
414
#endif
415
#if HAVE_LIBTIFF
416
    { "libtiff", 0, NULL, 0u },
417
#endif
418
    { "builtin", 0, NULL, 0u },
419
#if HAVE_WIC
420
    {
421
        "wic",
422
        0,
423
        g_subkeys_loader_wic_loader,
424
        sizeof(g_subkeys_loader_wic_loader)
425
            / sizeof(g_subkeys_loader_wic_loader[0])
426
    },
427
#endif
428
#if HAVE_COREGRAPHICS
429
    { "coregraphics", 0, NULL, 0u },
430
#endif
431
#ifdef HAVE_GDK_PIXBUF2
432
    { "gdk-pixbuf2", 0, NULL, 0u },
433
#endif
434
#if HAVE_GD
435
    { "gd", 0, NULL, 0u },
436
#endif
437
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
438
    { "quicklook", 0, NULL, 0u },
439
#endif
440
#if HAVE_FREEDESKTOP_THUMBNAILING
441
    { "gnome-thumbnailer", 0, NULL, 0u },
442
#endif
443
};
444

445
static sixel_option_argument_schema_t const g_schema_loaders_loader = {
446
    SIXEL_OPTFLAG_LOADERS,
447
    "--loaders",
448
    g_schema_loader_values_loader,
449
    sizeof(g_schema_loader_values_loader)
450
        / sizeof(g_schema_loader_values_loader[0])
451
};
452

453
static size_t
454
loader_token_name_length(char const *token, size_t token_length)
1,291✔
455
{
456
    size_t index;
566✔
457

458
    index = 0;
1,291✔
459
    while (index < token_length) {
15,058!
460
        if (token[index] == ':') {
13,283!
461
            break;
8✔
462
        }
463
        ++index;
13,267✔
464
    }
465

466
    return index;
2,357✔
467
}
566✔
468

469
static int
470
loader_parse_positive_int(char const *text, size_t length, int *value_out)
16✔
471
{
472
    size_t index;
8✔
473
    int value;
8✔
474
    unsigned char digit;
8✔
475

476
    if (text == NULL || value_out == NULL || length == 0) {
16!
477
        return 0;
478
    }
479

480
    value = 0;
8✔
481
    index = 0;
8✔
482
    while (index < length) {
48!
483
        digit = (unsigned char)text[index];
32✔
484
        if (digit < (unsigned char)'0' || digit > (unsigned char)'9') {
32!
485
            return 0;
486
        }
487
        if (value > (INT_MAX - 9) / 10) {
32!
488
            return 0;
489
        }
490
        value = value * 10 + (digit - (unsigned char)'0');
32✔
491
        ++index;
32✔
492
    }
493

494
    if (value <= 0) {
16!
495
        return 0;
496
    }
497

498
    *value_out = value;
16✔
499
    return 1;
16✔
500
}
2✔
501

502
static void
503
loader_apply_loader_suboptions(char const *order)
14,015✔
504
{
505
    char const *cursor;
8,248✔
506
    char const *token_start;
8,248✔
507
    char const *token_end;
8,248✔
508
    char const *order_end;
8,248✔
509
    size_t token_length;
8,248✔
510
    char token_buffer[256];
8,248✔
511
    char match_detail[128];
8,248✔
512
    sixel_option_argument_resolution_t resolution;
8,248✔
513
    size_t assignment_index;
8,248✔
514
    int parsed_value;
8,248✔
515

516
    cursor = order;
14,015✔
517
    token_start = order;
14,015✔
518
    token_end = order;
14,015✔
519
    order_end = NULL;
14,015✔
520
    token_length = 0;
14,015✔
521
    token_buffer[0] = '\0';
14,015✔
522
    match_detail[0] = '\0';
14,015✔
523
    resolution.resolved_base_value = 0;
14,015✔
524
    resolution.base_def = NULL;
14,015✔
525
    resolution.assignments = NULL;
14,015✔
526
    resolution.assignment_count = 0u;
14,015✔
527
    assignment_index = 0u;
14,015✔
528
    parsed_value = 0;
14,015✔
529

530
    sixel_helper_set_wic_ico_minsize(0);
14,015✔
531

532
    if (order == NULL || order[0] == '\0') {
14,015!
533
        return;
12,224✔
534
    }
535

536
    order_end = order + strlen(order);
1,791✔
537
    while (order_end > order && isspace((unsigned char)order_end[-1])) {
1,791!
538
        --order_end;
×
539
    }
540
    if (order_end > order && order_end[-1] == '!') {
1,791!
541
        --order_end;
1,791✔
542
    }
1,027✔
543

544
    token_start = order;
1,791✔
545
    cursor = order;
1,791✔
546
    while (cursor <= order_end) {
17,089✔
547
        if (cursor == order_end || *cursor == ',') {
15,298!
548
            token_end = cursor;
1,791✔
549
            while (token_start < token_end &&
1,791!
550
                   isspace((unsigned char)*token_start)) {
1,791!
551
                ++token_start;
×
552
            }
553
            while (token_end > token_start &&
1,791!
554
                   isspace((unsigned char)token_end[-1])) {
1,791!
555
                --token_end;
×
556
            }
557
            token_length = (size_t)(token_end - token_start);
1,791✔
558
            if (token_length > 0 && token_length < sizeof(token_buffer)) {
1,791!
559
                memcpy(token_buffer, token_start, token_length);
1,791✔
560
                token_buffer[token_length] = '\0';
1,791✔
561
                if (SIXEL_SUCCEEDED(sixel_option_parse_argument_with_suboptions(
2,730!
562
                        token_buffer,
563
                        &g_schema_loaders_loader,
564
                        &resolution,
565
                        match_detail,
566
                        sizeof(match_detail))) &&
1,791!
567
                    resolution.base_def != NULL &&
1,791!
568
                    strcmp(resolution.base_def->name, "wic") == 0) {
1,791!
569
                    assignment_index = 0u;
136✔
570
                    while (assignment_index < resolution.assignment_count) {
288!
571
                        if (strcmp(resolution.assignments[assignment_index]
18!
572
                                   .resolved_key_name,
2✔
573
                                   "ico_minsize") == 0 &&
16!
574
                            loader_parse_positive_int(
16✔
575
                                resolution.assignments[assignment_index]
8✔
576
                                .resolved_value_text,
8✔
577
                                strlen(resolution.assignments[assignment_index]
10✔
578
                                       .resolved_value_text),
16✔
579
                                &parsed_value)) {
580
                            sixel_helper_set_wic_ico_minsize(parsed_value);
16✔
581
                        }
2✔
582
                        ++assignment_index;
16✔
583
                    }
584
                }
34✔
585
                sixel_option_free_argument_resolution(&resolution);
1,791✔
586
            }
1,027✔
587
            token_start = cursor + 1;
1,791✔
588
        }
1,027✔
589
        ++cursor;
15,298✔
590
    }
591
}
6,860!
592

593
static int
594
loader_token_matches(char const *token,
3,708✔
595
                     size_t token_length,
596
                     char const *name)
597
{
598
    size_t index;
2,223✔
599
    unsigned char left;
2,223✔
600
    unsigned char right;
2,223✔
601

602
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
17,262!
603
        left = (unsigned char)token[index];
15,471✔
604
        right = (unsigned char)name[index];
15,471✔
605
        if (tolower(left) != tolower(right)) {
15,471✔
606
            return 0;
1,741✔
607
        }
608
    }
8,758✔
609

610
    if (index != token_length || name[index] != '\0') {
1,791!
611
        return 0;
×
612
    }
613

614
    return 1;
1,291✔
615
}
2,666✔
616

617
static sixel_loader_entry_t const *
618
loader_lookup_token(char const *token,
1,791✔
619
                    size_t token_length,
620
                    sixel_loader_entry_t const *entries,
621
                    size_t entry_count)
622
{
623
    size_t index;
1,066✔
624

625
    for (index = 0; index < entry_count; ++index) {
3,708!
626
        if (loader_token_matches(token,
6,374✔
627
                                 token_length,
2,666✔
628
                                 entries[index].name)) {
3,708✔
629
            return &entries[index];
1,291✔
630
        }
631
    }
1,639✔
632

633
    return NULL;
634
}
1,027✔
635

636
/*
637
 * loader_build_plan
638
 *
639
 * Translate a comma separated list into an execution plan that reorders the
640
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
641
 * are ignored so that new builds remain forward compatible.
642
 *
643
 * When the input ends with "!", the default fallback list is suppressed so
644
 * only the explicit tokens are tried.
645
 *
646
 *    user input "gd,coregraphics"
647
 *                |
648
 *                v
649
 *        +-------------------+
650
 *        | prioritized list  |
651
 *        +-------------------+
652
 *                |
653
 *                v
654
 *        +-------------------+
655
 *        | default fallbacks |
656
 *        +-------------------+
657
 */
658
static size_t
659
loader_build_plan(char const *order,
14,015✔
660
                  sixel_loader_entry_t const *entries,
661
                  size_t entry_count,
662
                  sixel_loader_entry_t const **plan,
663
                  size_t plan_capacity)
664
{
665
    size_t plan_length;
8,248✔
666
    size_t index;
8,248✔
667
    char const *cursor;
8,248✔
668
    char const *token_start;
8,248✔
669
    char const *token_end;
8,248✔
670
    char const *order_end;
8,248✔
671
    size_t token_length;
8,248✔
672
    sixel_loader_entry_t const *entry;
8,248✔
673
    size_t limit;
8,248✔
674
    int allow_fallback;
8,248✔
675

676
    plan_length = 0;
14,015✔
677
    index = 0;
14,015✔
678
    cursor = order;
14,015✔
679
    token_start = order;
14,015✔
680
    token_end = order;
14,015✔
681
    order_end = NULL;
14,015✔
682
    token_length = 0;
14,015✔
683
    entry = NULL;
14,015✔
684
    limit = plan_capacity;
14,015✔
685
    allow_fallback = 1;
14,015✔
686

687
    if (order != NULL) {
14,015✔
688
        order_end = order + strlen(order);
1,791✔
689
        while (order_end > order &&
1,791!
690
               isspace((unsigned char)order_end[-1])) {
1,791!
691
            --order_end;
×
692
        }
693
        if (order_end > order && order_end[-1] == '!') {
1,791!
694
            allow_fallback = 0;
1,791✔
695
            --order_end;
1,791✔
696
            while (order_end > order &&
1,791!
697
                   isspace((unsigned char)order_end[-1])) {
1,791!
698
                --order_end;
×
699
            }
700
        }
1,027✔
701
    }
1,027✔
702

703
    if (order != NULL && plan != NULL && plan_capacity > 0) {
13,207!
704
        token_start = order;
1,291✔
705
        cursor = order;
1,291✔
706
        while (cursor < order_end) {
15,298✔
707
            if (*cursor == ',') {
13,507!
708
                token_end = cursor;
×
709
                while (token_start < token_end &&
×
710
                       isspace((unsigned char)*token_start)) {
×
711
                    ++token_start;
×
712
                }
713
                while (token_end > token_start &&
×
714
                       isspace((unsigned char)token_end[-1])) {
×
715
                    --token_end;
×
716
                }
717
                token_length = (size_t)(token_end - token_start);
×
718
                if (token_length > 0) {
×
719
                    entry = loader_lookup_token(token_start,
×
720
                                                loader_token_name_length(
721
                                                    token_start,
722
                                                    token_length),
723
                                                entries,
724
                                                entry_count);
725
                    if (entry != NULL &&
×
726
                        !loader_plan_contains(plan,
×
727
                                              plan_length,
728
                                              entry) &&
×
729
                        plan_length < limit) {
730
                        plan[plan_length] = entry;
×
731
                        ++plan_length;
×
732
                    }
733
                }
734
                token_start = cursor + 1;
×
735
            }
736
            ++cursor;
13,507✔
737
        }
738

739
        token_end = order_end;
1,791✔
740
        while (token_start < token_end &&
1,791!
741
               isspace((unsigned char)*token_start)) {
1,791!
742
            ++token_start;
×
743
        }
744
        while (token_end > token_start &&
1,791!
745
               isspace((unsigned char)token_end[-1])) {
1,791!
746
            --token_end;
×
747
        }
748
        token_length = (size_t)(token_end - token_start);
1,791✔
749
        if (token_length > 0) {
1,791!
750
            entry = loader_lookup_token(token_start,
2,818✔
751
                                        loader_token_name_length(
1,027✔
752
                                            token_start,
1,027✔
753
                                            token_length),
1,027✔
754
                                        entries,
1,027✔
755
                                        entry_count);
1,027✔
756
            if (entry != NULL &&
2,994!
757
                !loader_plan_contains(plan, plan_length, entry) &&
2,055!
758
                plan_length < limit) {
1,027✔
759
                plan[plan_length] = entry;
1,791✔
760
                ++plan_length;
1,791✔
761
            }
1,027✔
762
        }
1,027✔
763
    }
1,027✔
764

765
    if (allow_fallback) {
13,832✔
766
        for (index = 0; index < entry_count && plan_length < limit; ++index) {
56,048✔
767
            entry = &entries[index];
43,824✔
768
            if (!entry->default_enabled) {
43,824✔
769
                continue;
6,444✔
770
            }
771
            if (!loader_plan_contains(plan, plan_length, entry)) {
37,380!
772
                plan[plan_length] = entry;
37,380✔
773
                ++plan_length;
37,380✔
774
            }
25,932✔
775
        }
25,932✔
776
    }
5,833✔
777

778
    return plan_length;
17,541✔
779
}
3,526✔
780

781
static void
782
loader_append_chunk(char *dest,
206✔
783
                    size_t capacity,
784
                    size_t *offset,
785
                    char const *chunk)
786
{
787
    size_t available;
132✔
788
    size_t length;
132✔
789

790
    if (dest == NULL || offset == NULL || chunk == NULL) {
206!
791
        return;
792
    }
793

794
    if (*offset >= capacity) {
206!
795
        return;
796
    }
797

798
    available = capacity - *offset;
206✔
799
    if (available == 0) {
206✔
800
        return;
801
    }
802

803
    length = strlen(chunk);
206✔
804
    if (length >= available) {
206!
805
        if (available == 0) {
×
806
            return;
807
        }
808
        length = available - 1u;
×
809
    }
810

811
    if (length > 0) {
206!
812
        memcpy(dest + *offset, chunk, length);
206✔
813
        *offset += length;
206✔
814
    }
206✔
815

816
    if (*offset < capacity) {
206!
817
        dest[*offset] = '\0';
206✔
818
    } else {
206✔
819
        dest[capacity - 1u] = '\0';
×
820
    }
821
}
206!
822

823
static void
824
loader_append_key_value(char *dest,
98✔
825
                        size_t capacity,
826
                        size_t *offset,
827
                        char const *label,
828
                        char const *value)
829
{
830
    char line[128];
60✔
831
    int written;
60✔
832

833
    if (value == NULL || value[0] == '\0') {
98!
834
        return;
×
835
    }
836

837
    written = sixel_compat_snprintf(line,
196✔
838
                                    sizeof(line),
839
                                    "  %-10s: %s\n",
840
                                    label,
98✔
841
                                    value);
98✔
842
    if (written < 0) {
98!
843
        return;
844
    }
845

846
    if ((size_t)written >= sizeof(line)) {
98!
847
        line[sizeof(line) - 1u] = '\0';
×
848
    }
849

850
    loader_append_chunk(dest, capacity, offset, line);
98✔
851
}
98!
852

853
static void
854
loader_extract_extension(char const *path, char *buffer, size_t capacity)
27✔
855
{
856
    char const *dot;
18✔
857
    size_t index;
18✔
858

859
    if (buffer == NULL || capacity == 0) {
27!
860
        return;
861
    }
862

863
    buffer[0] = '\0';
27✔
864

865
    if (path == NULL) {
27!
866
        return;
867
    }
868

869
    dot = strrchr(path, '.');
27✔
870
    if (dot == NULL || dot[1] == '\0') {
27!
871
        return;
15✔
872
    }
873

874
#if defined(_WIN32)
875
    {
876
        char const *slash;
877
        char const *backslash;
878

879
        slash = strrchr(path, '/');
880
        backslash = strrchr(path, '\\');
881
        if ((slash != NULL && dot < slash) ||
882
                (backslash != NULL && dot < backslash)) {
883
            return;
884
        }
885
    }
886
#else
887
    {
888
        char const *slash;
8✔
889

890
        slash = strrchr(path, '/');
12✔
891
        if (slash != NULL && dot < slash) {
12!
892
            return;
893
        }
894
    }
8!
895
#endif
896

897
    if (dot[1] == '\0') {
12✔
898
        return;
899
    }
900

901
    dot += 1;
12✔
902

903
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
48!
904
        buffer[index] = (char)tolower((unsigned char)dot[index]);
36✔
905
    }
36✔
906
    buffer[index] = '\0';
12✔
907
}
27!
908

909

910

911

912

913

914

915

916

917

918

919
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
920
static void
921
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
16✔
922
{
923
    if (buffer == NULL || capacity == 0) {
16!
924
        return;
925
    }
926

927
    buffer[0] = '\0';
16✔
928
    if (source == NULL) {
16!
929
        return;
930
    }
931

932
    if (!CFStringGetCString(source,
32!
933
                             buffer,
16✔
934
                             (CFIndex)capacity,
16✔
935
                             kCFStringEncodingUTF8)) {
936
        buffer[0] = '\0';
937
    }
938
}
16✔
939
#endif
940

941

942
static void
943
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
27✔
944
                          char const *filename)
945
{
946
    enum { description_length = 128 };
947
    enum { uttype_length = 128 };
948
    enum { extension_length = 32 };
949
    enum { message_length = 768 };
950
    char message[message_length];
18✔
951
    char type_value[description_length];
18✔
952
    char extension_text[extension_length + 2];
18✔
953
    char uttype[uttype_length];
18✔
954
    char desc_buffer[description_length];
18✔
955
    char extension[extension_length];
18✔
956
    char const *path;
18✔
957
    char const *display_path;
18✔
958
    char const *metadata_path;
18✔
959
    char const *description_text;
18✔
960
    char *mime_string;
18✔
961
    char *description_string;
18✔
962
    size_t offset;
18✔
963
    int gnome_available;
18✔
964
    int gnome_has_dirs;
18✔
965
    int gnome_has_match;
18✔
966
    int suggestions;
18✔
967

968
    message[0] = '\0';
27✔
969
    type_value[0] = '\0';
27✔
970
    extension_text[0] = '\0';
27✔
971
    uttype[0] = '\0';
27✔
972
    desc_buffer[0] = '\0';
27✔
973
    extension[0] = '\0';
27✔
974
    path = NULL;
27✔
975
    display_path = "(stdin)";
27✔
976
    metadata_path = NULL;
27✔
977
    description_text = NULL;
27✔
978
    mime_string = NULL;
27✔
979
    description_string = NULL;
27✔
980
    offset = 0u;
27✔
981
    gnome_available = 0;
27✔
982
    gnome_has_dirs = 0;
27✔
983
    gnome_has_match = 0;
27✔
984
    suggestions = 0;
27✔
985

986
    if (pchunk != NULL && pchunk->source_path != NULL) {
27!
987
        path = pchunk->source_path;
24✔
988
    } else if (filename != NULL) {
27!
989
        path = filename;
3✔
990
    }
3✔
991

992
    if (path != NULL && strcmp(path, "-") != 0) {
27!
993
        display_path = path;
24✔
994
    }
24✔
995

996
    if (path != NULL && strcmp(path, "-") != 0 &&
27!
997
            strstr(path, "://") == NULL) {
24!
998
        metadata_path = path;
24✔
999
    }
24✔
1000

1001
    loader_extract_extension(path, extension, sizeof(extension));
26✔
1002

1003
#if HAVE_FREEDESKTOP_THUMBNAILING
1004
    if (metadata_path != NULL) {
26!
1005
        /*
1006
         * Collect MIME metadata via file(1) when fork() and friends are
1007
         * available.  Windows builds compiled with clang64 lack these
1008
         * interfaces, so the thumbnail helpers remain disabled there.
1009
         */
1010
        mime_string = thumbnailer_guess_content_type(metadata_path);
24✔
1011
        description_string = thumbnailer_run_file(metadata_path, NULL);
24✔
1012
    }
24✔
1013
#else
1014
    (void)metadata_path;
1015
#endif
1016

1017
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1018
#if defined(__clang__)
1019
    /*
1020
     * Allow use of legacy UTType C APIs when compiling with the
1021
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
1022
     * so we must intentionally silence the deprecation warnings here.
1023
     */
1024
#pragma clang diagnostic push
1025
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
1026
#endif
1027
    {
1028
        CFStringRef uti_ref;
1029
        CFStringRef mime_ref;
1030
        CFStringRef ext_ref;
1031
        CFStringRef desc_ref;
1032
        CFStringRef preferred_mime;
1033
        char uti_local[uttype_length];
1034
        char desc_local[description_length];
1035
        char mime_local[64];
1036

1037
        uti_ref = NULL;
8✔
1038
        mime_ref = NULL;
8✔
1039
        ext_ref = NULL;
8✔
1040
        desc_ref = NULL;
8✔
1041
        preferred_mime = NULL;
8✔
1042
        uti_local[0] = '\0';
8✔
1043
        desc_local[0] = '\0';
8✔
1044
        mime_local[0] = '\0';
8✔
1045

1046
        if (mime_string != NULL) {
8!
1047
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
16✔
1048
                                                 mime_string,
8✔
1049
                                                 kCFStringEncodingUTF8);
1050
        }
8✔
1051
        if (mime_ref != NULL) {
8!
1052
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
8✔
1053
                kUTTagClassMIMEType,
8✔
1054
                mime_ref,
8✔
1055
                NULL);
1056
        }
8✔
1057
        if (uti_ref == NULL && extension[0] != '\0') {
15!
1058
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
1059
                                                extension,
1060
                                                kCFStringEncodingUTF8);
1061
            if (ext_ref != NULL) {
×
1062
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
1063
                    kUTTagClassFilenameExtension,
1064
                    ext_ref,
1065
                    NULL);
1066
            }
1067
        }
1068
        if (uti_ref != NULL) {
8!
1069
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
8✔
1070
            desc_ref = UTTypeCopyDescription(uti_ref);
8✔
1071
            if (desc_ref != NULL) {
8!
1072
                loader_copy_cfstring(desc_ref,
16✔
1073
                                     desc_local,
8✔
1074
                                     sizeof(desc_local));
1075
                CFRelease(desc_ref);
8✔
1076
                desc_ref = NULL;
8✔
1077
            }
8✔
1078
            if (mime_string == NULL) {
16!
1079
                preferred_mime = UTTypeCopyPreferredTagWithClass(
1080
                    uti_ref,
1081
                    kUTTagClassMIMEType);
1082
                if (preferred_mime != NULL) {
×
1083
                    loader_copy_cfstring(preferred_mime,
1084
                                         mime_local,
1085
                                         sizeof(mime_local));
1086
                    CFRelease(preferred_mime);
1087
                    preferred_mime = NULL;
1088
                }
1089
                if (mime_local[0] != '\0') {
×
1090
                    mime_string = thumbnailer_strdup(mime_local);
1091
                }
1092
            }
1093
        }
8✔
1094
        if (mime_ref != NULL) {
8!
1095
            CFRelease(mime_ref);
8✔
1096
        }
8✔
1097
        if (ext_ref != NULL) {
16!
1098
            CFRelease(ext_ref);
1099
        }
1100
        if (uti_ref != NULL) {
8!
1101
            CFRelease(uti_ref);
8✔
1102
        }
8✔
1103
        if (uti_local[0] != '\0') {
8!
1104
            sixel_compat_snprintf(uttype,
16✔
1105
                                  sizeof(uttype),
1106
                                  "%s",
1107
                                  uti_local);
8✔
1108
        }
8✔
1109
        if (desc_local[0] != '\0') {
8!
1110
            sixel_compat_snprintf(desc_buffer,
16✔
1111
                                  sizeof(desc_buffer),
1112
                                  "%s",
1113
                                  desc_local);
8✔
1114
        }
8✔
1115
    }
1116
#if defined(__clang__)
1117
#pragma clang diagnostic pop
1118
#endif
1119
#endif
1120

1121
    if (description_string != NULL && description_string[0] != '\0') {
26!
1122
        description_text = description_string;
24✔
1123
    } else if (desc_buffer[0] != '\0') {
27!
1124
        description_text = desc_buffer;
1125
    } else {
1126
        description_text = "unknown content";
3✔
1127
    }
1128

1129
    sixel_compat_snprintf(type_value,
54✔
1130
                          sizeof(type_value),
1131
                          "%s",
1132
                          description_text);
27✔
1133

1134
    loader_append_chunk(message,
27✔
1135
                        sizeof(message),
1136
                        &offset,
1137
                        "diagnostic:\n");
1138
    loader_append_key_value(message,
54✔
1139
                            sizeof(message),
1140
                            &offset,
1141
                            "file",
1142
                            display_path);
27✔
1143
    loader_append_key_value(message,
54✔
1144
                            sizeof(message),
1145
                            &offset,
1146
                            "type",
1147
                            type_value);
27✔
1148

1149
    if (mime_string != NULL && mime_string[0] != '\0') {
27!
1150
        loader_append_key_value(message,
48✔
1151
                                sizeof(message),
1152
                                &offset,
1153
                                "mime",
1154
                                mime_string);
24✔
1155
    }
24✔
1156

1157
    if (uttype[0] != '\0') {
26!
1158
        loader_append_key_value(message,
16✔
1159
                                sizeof(message),
1160
                                &offset,
1161
                                "uti",
1162
                                uttype);
8✔
1163
    }
8✔
1164

1165
    if (extension[0] != '\0') {
30!
1166
        sixel_compat_snprintf(extension_text,
24✔
1167
                              sizeof(extension_text),
1168
                              ".%s",
1169
                              extension);
12✔
1170
        loader_append_key_value(message,
24✔
1171
                                sizeof(message),
1172
                                &offset,
1173
                                "extension",
1174
                                extension_text);
12✔
1175
    }
12✔
1176

1177
    loader_append_chunk(message,
27✔
1178
                        sizeof(message),
1179
                        &offset,
1180
                        "  suggestions:\n");
1181

1182
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1183
    int quicklook_available;
1184
    int quicklook_supported;
1185

1186
    quicklook_available = 0;
9✔
1187
    quicklook_supported = 0;
9✔
1188

1189
    quicklook_available = loader_registry_entry_available("quicklook");
9✔
1190
    if (quicklook_available) {
9!
1191
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
9✔
1192
    }
9✔
1193
    if (quicklook_supported) {
18!
1194
        loader_append_chunk(message,
1195
                            sizeof(message),
1196
                            &offset,
1197
                            "    - QuickLook rendered a preview during "
1198
                            "the probe; try -L quicklook.\n");
1199
        suggestions += 1;
1200
    }
1201
#endif
1202

1203
#if HAVE_FREEDESKTOP_THUMBNAILING
1204
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
27✔
1205
    if (gnome_available) {
27!
1206
        loader_probe_gnome_thumbnailers(mime_string,
27✔
1207
                                        &gnome_has_dirs,
1208
                                        &gnome_has_match);
1209
        if (gnome_has_dirs && gnome_has_match) {
27!
1210
            loader_append_chunk(message,
1211
                                sizeof(message),
1212
                                &offset,
1213
                                "    - GNOME thumbnailer definitions match "
1214
                                "this MIME type; try -L gnome-thumbnailer.\n"
1215
                                );
1216
            suggestions += 1;
1217
        }
1218
    }
27✔
1219
#else
1220
    (void)gnome_available;
1221
    (void)gnome_has_dirs;
1222
    (void)gnome_has_match;
1223
#endif
1224

1225
    if (suggestions == 0) {
27!
1226
        loader_append_chunk(message,
27✔
1227
                            sizeof(message),
1228
                            &offset,
1229
                            "    (no thumbnail helper hints)\n");
1230
    }
27✔
1231

1232
    if (suggestions > 0) {
27!
1233
        loader_append_chunk(message,
1234
                            sizeof(message),
1235
                            &offset,
1236
                            "  hint       : Enable one of the suggested "
1237
                            "loaders with -L.\n");
1238
    } else {
1239
        loader_append_chunk(message,
27✔
1240
                            sizeof(message),
1241
                            &offset,
1242
                            "  hint       : Convert the file to PNG or "
1243
                            "enable optional loaders.\n");
1244
    }
1245

1246
    sixel_helper_set_additional_message(message);
27✔
1247

1248
    free(mime_string);
27✔
1249
    free(description_string);
27✔
1250
}
27✔
1251

1252
SIXELAPI SIXELSTATUS
1253
sixel_loader_new(
14,071✔
1254
    sixel_loader_t   /* out */ **pploader,
1255
    sixel_allocator_t/* in */  *allocator)
1256
{
1257
    SIXELSTATUS status = SIXEL_FALSE;
14,071✔
1258
    sixel_loader_t *loader;
8,276✔
1259
    sixel_allocator_t *local_allocator;
8,276✔
1260

1261
    loader = NULL;
14,071✔
1262
    local_allocator = allocator;
14,071✔
1263

1264
    if (pploader == NULL) {
14,071!
1265
        sixel_helper_set_additional_message(
×
1266
            "sixel_loader_new: pploader is null.");
1267
        status = SIXEL_BAD_ARGUMENT;
×
1268
        goto end;
×
1269
    }
1270

1271
    if (local_allocator == NULL) {
14,071!
1272
        status = sixel_allocator_new(&local_allocator,
×
1273
                                     NULL,
1274
                                     NULL,
1275
                                     NULL,
1276
                                     NULL);
1277
        if (SIXEL_FAILED(status)) {
×
1278
            goto end;
×
1279
        }
1280
    } else {
1281
        sixel_allocator_ref(local_allocator);
14,071✔
1282
    }
1283

1284
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
14,071✔
1285
                                                      sizeof(*loader));
1286
    if (loader == NULL) {
14,071!
1287
        sixel_helper_set_additional_message(
×
1288
            "sixel_loader_new: sixel_allocator_malloc() failed.");
1289
        status = SIXEL_BAD_ALLOCATION;
×
1290
        sixel_allocator_unref(local_allocator);
×
1291
        goto end;
×
1292
    }
1293

1294
    loader->ref = 1U;
14,071✔
1295
    loader->fstatic = 0;
14,071✔
1296
    loader->fuse_palette = 0;
14,071✔
1297
    loader->reqcolors = SIXEL_PALETTE_MAX;
14,071✔
1298
    loader->bgcolor[0] = 0;
14,071✔
1299
    loader->bgcolor[1] = 0;
14,071✔
1300
    loader->bgcolor[2] = 0;
14,071✔
1301
    loader->has_bgcolor = 0;
14,071✔
1302
    loader->loop_control = SIXEL_LOOP_AUTO;
14,071✔
1303
    loader->finsecure = 0;
14,071✔
1304
    loader->has_start_frame_no = 0;
14,071✔
1305
    loader->start_frame_no = INT_MIN;
14,071✔
1306
    loader->cancel_flag = NULL;
14,071✔
1307
    loader->context = NULL;
14,071✔
1308
    /*
1309
     * Initialize a private logger. The helper reuses an existing global
1310
     * logger sink when present so loader markers share the timeline with
1311
     * upstream stages without requiring sixel_loader_setopt().
1312
     */
1313
    sixel_logger_init(&loader->logger);
14,071✔
1314
    (void)sixel_logger_prepare_env(&loader->logger);
14,071✔
1315
    loader->loader_order = NULL;
14,071✔
1316
    loader->allocator = local_allocator;
14,071✔
1317
    loader->last_loader_name[0] = '\0';
14,071✔
1318
    loader->last_source_path[0] = '\0';
14,071✔
1319
    loader->last_input_bytes = 0u;
14,071✔
1320
    loader->callback_failed = 0;
14,071✔
1321
    loader->log_loader_finished = 0;
14,071✔
1322
    loader->log_path[0] = '\0';
14,071✔
1323
    loader->log_loader_name[0] = '\0';
14,071✔
1324
    loader->log_input_bytes = 0u;
14,071✔
1325

1326
    *pploader = loader;
14,071✔
1327
    status = SIXEL_OK;
14,071✔
1328

1329
end:
7,179✔
1330
    return status;
17,613✔
1331
}
3,542✔
1332

1333
SIXELAPI void
1334
sixel_loader_ref(
127,437✔
1335
    sixel_loader_t /* in */ *loader)
1336
{
1337
    if (loader == NULL) {
127,437!
1338
        return;
1339
    }
1340

1341
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
127,437✔
1342
}
62,652✔
1343

1344
SIXELAPI void
1345
sixel_loader_unref(
141,508✔
1346
    sixel_loader_t /* in */ *loader)
1347
{
1348
    sixel_allocator_t *allocator;
83,248✔
1349
    unsigned int previous;
83,248✔
1350

1351
    if (loader == NULL) {
141,508!
1352
        return;
1353
    }
1354

1355
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
141,508✔
1356
    if (previous == 1U) {
141,508✔
1357
        allocator = loader->allocator;
14,071✔
1358
        sixel_logger_close(&loader->logger);
14,071✔
1359
        sixel_allocator_free(allocator, loader->loader_order);
14,071✔
1360
        sixel_allocator_free(allocator, loader);
14,071✔
1361
        sixel_allocator_unref(allocator);
14,071✔
1362
    }
6,892✔
1363
}
69,544!
1364

1365
SIXELAPI SIXELSTATUS
1366
sixel_loader_setopt(
113,366✔
1367
    sixel_loader_t /* in */ *loader,
1368
    int            /* in */ option,
1369
    void const     /* in */ *value)
1370
{
1371
    SIXELSTATUS status = SIXEL_FALSE;
113,366✔
1372
    int const *flag;
66,696✔
1373
    unsigned char const *color;
66,696✔
1374
    char const *order;
66,696✔
1375
    char *copy;
66,696✔
1376
    sixel_allocator_t *allocator;
66,696✔
1377

1378
    flag = NULL;
113,366✔
1379
    color = NULL;
113,366✔
1380
    order = NULL;
113,366✔
1381
    copy = NULL;
113,366✔
1382
    allocator = NULL;
113,366✔
1383

1384
    if (loader == NULL) {
113,366!
1385
        sixel_helper_set_additional_message(
×
1386
            "sixel_loader_setopt: loader is null.");
1387
        status = SIXEL_BAD_ARGUMENT;
×
1388
        goto end0;
×
1389
    }
1390

1391
    sixel_loader_ref(loader);
113,366✔
1392

1393
    switch (option) {
113,366!
1394
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
7,179✔
1395
        flag = (int const *)value;
14,071✔
1396
        loader->fstatic = flag != NULL ? *flag : 0;
14,071!
1397
        status = SIXEL_OK;
14,071✔
1398
        break;
14,071✔
1399
    case SIXEL_LOADER_OPTION_USE_PALETTE:
7,179✔
1400
        flag = (int const *)value;
14,071✔
1401
        loader->fuse_palette = flag != NULL ? *flag : 0;
14,071!
1402
        status = SIXEL_OK;
14,071✔
1403
        break;
14,071✔
1404
    case SIXEL_LOADER_OPTION_REQCOLORS:
7,179✔
1405
        flag = (int const *)value;
14,071✔
1406
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
14,071!
1407
        if (loader->reqcolors < 1) {
14,071!
1408
            sixel_helper_set_additional_message(
×
1409
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1410
            status = SIXEL_BAD_ARGUMENT;
×
1411
            goto end;
×
1412
        }
1413
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
14,071!
1414
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1415
        }
1416
        status = SIXEL_OK;
9,337✔
1417
        break;
9,337✔
1418
    case SIXEL_LOADER_OPTION_BGCOLOR:
3,633✔
1419
        if (value == NULL) {
7,235✔
1420
            loader->has_bgcolor = 0;
7,099✔
1421
        } else {
3,511✔
1422
            color = (unsigned char const *)value;
136✔
1423
            loader->bgcolor[0] = color[0];
136✔
1424
            loader->bgcolor[1] = color[1];
136✔
1425
            loader->bgcolor[2] = color[2];
136✔
1426
            loader->has_bgcolor = 1;
136✔
1427
        }
1428
        status = SIXEL_OK;
4,829✔
1429
        break;
4,829✔
1430
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
7,179✔
1431
        flag = (int const *)value;
14,071✔
1432
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
14,071!
1433
        status = SIXEL_OK;
14,071✔
1434
        break;
14,071✔
1435
    case SIXEL_LOADER_OPTION_INSECURE:
7,179✔
1436
        flag = (int const *)value;
14,071✔
1437
        loader->finsecure = flag != NULL ? *flag : 0;
14,071!
1438
        status = SIXEL_OK;
14,071✔
1439
        break;
14,071✔
1440
    case SIXEL_LOADER_OPTION_START_FRAME_NO:
3,633✔
1441
        if (value == NULL) {
7,235✔
1442
            loader->has_start_frame_no = 0;
7,097✔
1443
            loader->start_frame_no = INT_MIN;
7,097✔
1444
        } else {
3,508✔
1445
            flag = (int const *)value;
138✔
1446
            loader->start_frame_no = *flag;
138✔
1447
            loader->has_start_frame_no = 1;
138✔
1448
        }
1449
        status = SIXEL_OK;
4,829✔
1450
        break;
4,829✔
1451
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
3,633✔
1452
        loader->cancel_flag = (int const *)value;
7,235✔
1453
        status = SIXEL_OK;
7,235✔
1454
        break;
7,235✔
1455
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
3,633✔
1456
        allocator = loader->allocator;
7,235✔
1457
        sixel_allocator_free(allocator, loader->loader_order);
7,235✔
1458
        loader->loader_order = NULL;
7,235✔
1459
        if (value != NULL) {
7,235✔
1460
            order = (char const *)value;
1,782✔
1461
            copy = loader_strdup(order, allocator);
1,782✔
1462
            if (copy == NULL) {
1,782!
1463
                sixel_helper_set_additional_message(
×
1464
                    "sixel_loader_setopt: loader_strdup() failed.");
1465
                status = SIXEL_BAD_ALLOCATION;
×
1466
                goto end;
×
1467
            }
1468
            loader->loader_order = copy;
1,782✔
1469
        }
1,020✔
1470
        status = SIXEL_OK;
4,829✔
1471
        break;
4,829✔
1472
    case SIXEL_LOADER_OPTION_CONTEXT:
7,179✔
1473
        loader->context = (void *)value;
14,071✔
1474
        status = SIXEL_OK;
14,071✔
1475
        break;
14,071✔
1476
    default:
1477
        sixel_helper_set_additional_message(
×
1478
            "sixel_loader_setopt: unknown option.");
1479
        status = SIXEL_BAD_ARGUMENT;
×
1480
        goto end;
×
1481
    }
55,760✔
1482

1483
end:
57,606✔
1484
    sixel_loader_unref(loader);
113,366✔
1485

1486
end0:
57,606✔
1487
    return status;
142,034✔
1488
}
28,668✔
1489

1490
SIXELAPI char const *
1491
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
13,332✔
1492
{
1493
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
13,332!
1494
        return NULL;
1495
    }
1496
    return loader->last_loader_name;
13,332✔
1497
}
6,600✔
1498

1499
SIXELAPI char const *
1500
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
13,059✔
1501
{
1502
    if (loader == NULL || loader->last_source_path[0] == '\0') {
13,059!
1503
        return NULL;
183✔
1504
    }
1505
    return loader->last_source_path;
12,786✔
1506
}
6,465✔
1507

1508
SIXELAPI size_t
1509
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
6,666✔
1510
{
1511
    if (loader == NULL) {
6,666!
1512
        return 0u;
1513
    }
1514
    return loader->last_input_bytes;
6,666✔
1515
}
3,300✔
1516

1517
int
1518
sixel_loader_get_start_frame_no(sixel_loader_t const *loader,
×
1519
                                int *start_frame_no)
1520
{
1521
    if (start_frame_no != NULL) {
×
1522
        *start_frame_no = INT_MIN;
×
1523
    }
1524
    if (loader == NULL || start_frame_no == NULL ||
×
1525
        loader->has_start_frame_no == 0) {
×
1526
        return 0;
1527
    }
1528

1529
    *start_frame_no = loader->start_frame_no;
×
1530
    return 1;
×
1531
}
1532

1533
SIXELAPI SIXELSTATUS
1534
sixel_loader_load_file(
14,071✔
1535
    sixel_loader_t         /* in */ *loader,
1536
    char const             /* in */ *filename,
1537
    sixel_load_image_function /* in */ fn_load)
1538
{
1539
    SIXELSTATUS status = SIXEL_FALSE;
14,071✔
1540
    sixel_chunk_t *pchunk;
8,276✔
1541
    sixel_loader_entry_t const **plan;
8,276✔
1542
    sixel_loader_entry_t const *entries;
8,276✔
1543
    size_t entry_count;
8,276✔
1544
    size_t plan_length;
8,276✔
1545
    size_t plan_index;
8,276✔
1546
    sixel_loader_component_t *component;
8,276✔
1547
    int reqcolors;
8,276✔
1548
    char const *order_override;
8,276✔
1549
    char const *env_order;
8,276✔
1550
    sixel_loader_callback_state_t callback_state;
8,276✔
1551

1552
    pchunk = NULL;
14,071✔
1553
    plan = NULL;
14,071✔
1554
    entries = NULL;
14,071✔
1555
    entry_count = 0;
14,071✔
1556
    plan_length = 0;
14,071✔
1557
    plan_index = 0;
14,071✔
1558
    component = NULL;
14,071✔
1559
    reqcolors = 0;
14,071✔
1560
    order_override = NULL;
14,071✔
1561
    env_order = NULL;
14,071✔
1562

1563
    if (loader == NULL) {
14,071!
1564
        sixel_helper_set_additional_message(
×
1565
            "sixel_loader_load_file: loader is null.");
1566
        status = SIXEL_BAD_ARGUMENT;
×
1567
        goto end0;
×
1568
    }
1569

1570
    sixel_loader_ref(loader);
14,071✔
1571

1572
    loader->log_loader_finished = 0;
14,071✔
1573
    loader->log_loader_name[0] = '\0';
14,071✔
1574
    loader->log_input_bytes = 0u;
14,071✔
1575
    loader->log_path[0] = '\0';
14,071✔
1576
    if (filename != NULL) {
14,071✔
1577
        (void)sixel_compat_snprintf(loader->log_path,
20,588✔
1578
                                    sizeof(loader->log_path),
1579
                                    "%s",
1580
                                    filename);
6,772✔
1581
    }
6,772✔
1582
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
15,036✔
1583

1584
    memset(&callback_state, 0, sizeof(callback_state));
15,036✔
1585
    callback_state.loader = loader;
15,036✔
1586
    callback_state.fn = fn_load;
15,036✔
1587
    callback_state.context = loader->context;
15,036✔
1588
    loader->callback_failed = 0;
15,036✔
1589

1590
    entry_count = loader_registry_get_entries(&entries);
15,036✔
1591

1592
    reqcolors = loader->reqcolors;
15,036✔
1593
    if (reqcolors > SIXEL_PALETTE_MAX) {
15,036!
1594
        reqcolors = SIXEL_PALETTE_MAX;
1595
    }
1596

1597
    status = sixel_chunk_new(&pchunk,
14,071✔
1598
                             filename,
6,892✔
1599
                             loader->finsecure,
6,892✔
1600
                             loader->cancel_flag,
6,892✔
1601
                             loader->allocator);
6,892✔
1602
    if (status != SIXEL_OK) {
14,071!
1603
        goto end;
56✔
1604
    }
1605

1606
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
14,015!
1607
        status = SIXEL_OK;
×
1608
        goto end;
×
1609
    }
1610

1611
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
13,997!
1612
        (void)sixel_compat_snprintf(loader->log_path,
20,442✔
1613
                                    sizeof(loader->log_path),
1614
                                    "%s",
1615
                                    pchunk->source_path);
9,099✔
1616
    }
6,717✔
1617

1618
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
14,015!
1619
        status = SIXEL_LOGIC_ERROR;
×
1620
        goto end;
×
1621
    }
1622

1623
    status = SIXEL_FALSE;
14,015✔
1624
    order_override = loader->loader_order;
14,015✔
1625
    /*
1626
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1627
     * a loader order via -L/--loaders or sixel_loader_setopt().
1628
     */
1629
    if (order_override == NULL) {
14,015✔
1630
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
12,233✔
1631
        if (env_order != NULL && env_order[0] != '\0') {
12,233!
1632
            order_override = env_order;
4,729✔
1633
        }
7✔
1634
    }
5,840✔
1635

1636
    loader_apply_loader_suboptions(order_override);
14,015✔
1637

1638
    plan = sixel_allocator_malloc(loader->allocator,
24,115✔
1639
                                  entry_count * sizeof(*plan));
12,533✔
1640
    if (plan == NULL) {
14,015!
1641
        status = SIXEL_BAD_ALLOCATION;
×
1642
        goto end;
×
1643
    }
1644

1645
    plan_length = loader_build_plan(order_override,
20,875✔
1646
                                    entries,
6,860✔
1647
                                    entry_count,
6,860✔
1648
                                    plan,
6,860✔
1649
                                    entry_count);
6,860✔
1650
    if (plan_length == 0u) {
14,015!
1651
        if (order_override != NULL && order_override[0] != '\0') {
×
1652
            sixel_helper_set_additional_message(
×
1653
                "sixel_loader_load_file: no supported loader in loader "
1654
                "order.");
1655
            status = SIXEL_BAD_ARGUMENT;
×
1656
        } else {
1657
            sixel_helper_set_additional_message(
×
1658
                "sixel_loader_load_file: no available loader backend.");
1659
            status = SIXEL_LOADER_FAILED;
×
1660
        }
1661
        goto end;
×
1662
    }
1663

1664
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
19,960✔
1665
        if (plan[plan_index] == NULL) {
19,685!
1666
            continue;
×
1667
        }
1668
        if (plan[plan_index]->predicate != NULL &&
19,986!
1669
            plan[plan_index]->predicate(pchunk) == 0) {
7,389✔
1670
            continue;
5,499✔
1671
        }
1672
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
14,186!
1673
        if (plan[plan_index]->name != NULL) {
14,186!
1674
            (void)sixel_compat_snprintf(loader->log_loader_name,
21,154✔
1675
                                        sizeof(loader->log_loader_name),
1676
                                        "%s",
1677
                                        plan[plan_index]->name);
9,428✔
1678
        } else {
6,968✔
1679
            loader->log_loader_name[0] = '\0';
×
1680
        }
1681
        component = sixel_loader_component_legacy_new(plan[plan_index],
21,154✔
1682
                                                   loader->allocator);
6,968✔
1683
        if (component == NULL) {
14,186!
NEW
1684
            status = SIXEL_BAD_ALLOCATION;
×
NEW
1685
            goto end;
×
1686
        }
1687

1688
        status = loader_apply_component_options(component, loader, reqcolors);
14,186✔
1689
        if (SIXEL_FAILED(status)) {
14,186!
NEW
1690
            sixel_loader_component_unref(component);
×
NEW
1691
            component = NULL;
×
NEW
1692
            goto end;
×
1693
        }
1694

1695
        loader_trace_try(sixel_loader_component_get_name(component));
14,186✔
1696
        status = sixel_loader_component_load(component,
21,154✔
1697
                                             pchunk,
6,968✔
1698
                                             loader_callback_trampoline,
1699
                                             &callback_state);
1700
        loader_trace_result(sixel_loader_component_get_name(component), status);
14,186✔
1701
        sixel_loader_component_unref(component);
14,186✔
1702
        component = NULL;
14,186✔
1703
        if (SIXEL_SUCCEEDED(status)) {
14,186✔
1704
            break;
9,096✔
1705
        }
1706
    }
266✔
1707

1708
    if (SIXEL_FAILED(status)) {
14,015✔
1709
        if (status == SIXEL_FALSE) {
275!
1710
            if (!loader->callback_failed &&
54!
1711
                    plan_index >= plan_length &&
27!
1712
                    pchunk != NULL) {
27!
1713
                status = SIXEL_LOADER_FAILED;
27✔
1714
                loader_publish_diagnostic(pchunk, filename);
27✔
1715
            } else {
27✔
1716
                sixel_helper_set_additional_message(
×
1717
                    "sixel_loader_load_file: loader returned "
1718
                    "unspecified failure.");
1719
                status = SIXEL_LOADER_FAILED;
×
1720
            }
1721
        }
27✔
1722
        goto end;
275✔
1723
    }
1724

1725
    if (plan_index < plan_length &&
19,644!
1726
            plan[plan_index] != NULL &&
13,740!
1727
            plan[plan_index]->name != NULL) {
13,740!
1728
        (void)sixel_compat_snprintf(loader->last_loader_name,
20,442✔
1729
                                    sizeof(loader->last_loader_name),
1730
                                    "%s",
1731
                                    plan[plan_index]->name);
9,096✔
1732
    } else {
6,702✔
1733
        loader->last_loader_name[0] = '\0';
×
1734
    }
1735
    loader->last_input_bytes = pchunk->size;
13,740✔
1736
    if (pchunk->source_path != NULL) {
20,442✔
1737
        size_t path_len;
7,927✔
1738

1739
        path_len = strlen(pchunk->source_path);
13,467✔
1740
        if (path_len >= sizeof(loader->last_source_path)) {
13,467!
1741
            path_len = sizeof(loader->last_source_path) - 1u;
1742
        }
1743
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
13,467✔
1744
        loader->last_source_path[path_len] = '\0';
13,467✔
1745
    } else {
6,567✔
1746
        loader->last_source_path[0] = '\0';
273✔
1747
    }
1748

1749
end:
2,445✔
1750
    if (component != NULL) {
14,071!
1751
        sixel_loader_component_unref(component);
1752
        component = NULL;
1753
    }
1754
    if (plan != NULL) {
14,067!
1755
        sixel_allocator_free(loader->allocator, plan);
14,015✔
1756
        plan = NULL;
14,015✔
1757
    }
6,860✔
1758
    sixel_chunk_destroy(pchunk);
14,071✔
1759
    sixel_loader_unref(loader);
14,071✔
1760

1761
end0:
7,179✔
1762
    return status;
17,613✔
1763
}
3,542✔
1764

1765
/* load image from file */
1766

1767
SIXELAPI SIXELSTATUS
1768
sixel_helper_load_image_file(
×
1769
    char const                /* in */     *filename,     /* source file name */
1770
    int                       /* in */     fstatic,       /* whether to */
1771
                                                             /* extract a */
1772
                                                             /* static image */
1773
                                                             /* from an */
1774
                                                             /* animated gif */
1775
    int                       /* in */     fuse_palette,  /* whether to */
1776
                                                             /* use a */
1777
                                                             /* paletted */
1778
                                                             /* image; set */
1779
                                                             /* non-zero to */
1780
                                                             /* request one */
1781
    int                       /* in */     reqcolors,     /* requested */
1782
                                                             /* number of */
1783
                                                             /* colors; */
1784
                                                             /* should be */
1785
                                                             /* equal to or */
1786
                                                             /* less than */
1787
                                                             /* SIXEL_ */
1788
                                                             /* PALETTE_ */
1789
                                                             /* MAX */
1790
    unsigned char             /* in */     *bgcolor,      /* background */
1791
                                                             /* color, may */
1792
                                                             /* be NULL */
1793
    int                       /* in */     loop_control,  /* one of enum */
1794
                                                             /* loopControl */
1795
    sixel_load_image_function /* in */     fn_load,       /* callback */
1796
    int                       /* in */     finsecure,     /* true if do */
1797
                                                             /* not verify */
1798
                                                             /* SSL */
1799
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
1800
                                                             /* may be */
1801
                                                             /* NULL */
1802
    void                      /* in/out */ *context,      /* private data */
1803
                                                             /* passed to */
1804
                                                             /* callback */
1805
                                                             /* function, */
1806
                                                             /* may be */
1807
                                                             /* NULL */
1808
    sixel_allocator_t         /* in */     *allocator     /* allocator */
1809
                                                             /* object, */
1810
                                                             /* may be */
1811
                                                             /* NULL */
1812
)
1813
{
1814
    SIXELSTATUS status = SIXEL_FALSE;
×
1815
    sixel_loader_t *loader;
1816

1817
    loader = NULL;
×
1818

1819
    status = sixel_loader_new(&loader, allocator);
×
1820
    if (SIXEL_FAILED(status)) {
×
1821
        goto end;
×
1822
    }
1823

1824
    status = sixel_loader_setopt(loader,
×
1825
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1826
                                 &fstatic);
1827
    if (SIXEL_FAILED(status)) {
×
1828
        goto end;
×
1829
    }
1830

1831
    status = sixel_loader_setopt(loader,
×
1832
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1833
                                 &fuse_palette);
1834
    if (SIXEL_FAILED(status)) {
×
1835
        goto end;
×
1836
    }
1837

1838
    status = sixel_loader_setopt(loader,
×
1839
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1840
                                 &reqcolors);
1841
    if (SIXEL_FAILED(status)) {
×
1842
        goto end;
×
1843
    }
1844

1845
    status = sixel_loader_setopt(loader,
×
1846
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1847
                                 bgcolor);
1848
    if (SIXEL_FAILED(status)) {
×
1849
        goto end;
×
1850
    }
1851

1852
    status = sixel_loader_setopt(loader,
×
1853
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1854
                                 &loop_control);
1855
    if (SIXEL_FAILED(status)) {
×
1856
        goto end;
×
1857
    }
1858

1859
    status = sixel_loader_setopt(loader,
×
1860
                                 SIXEL_LOADER_OPTION_INSECURE,
1861
                                 &finsecure);
1862
    if (SIXEL_FAILED(status)) {
×
1863
        goto end;
×
1864
    }
1865

1866
    status = sixel_loader_setopt(loader,
×
1867
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1868
                                 cancel_flag);
1869
    if (SIXEL_FAILED(status)) {
×
1870
        goto end;
×
1871
    }
1872

1873
    status = sixel_loader_setopt(loader,
×
1874
                                 SIXEL_LOADER_OPTION_CONTEXT,
1875
                                 context);
1876
    if (SIXEL_FAILED(status)) {
×
1877
        goto end;
×
1878
    }
1879

1880
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1881

1882
end:
1883
    sixel_loader_unref(loader);
×
1884

1885
    return status;
×
1886
}
1887

1888

1889
SIXELAPI size_t
1890
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
136✔
1891
{
1892
    sixel_loader_entry_t const *entries;
80✔
1893
    size_t entry_count;
80✔
1894
    size_t limit;
80✔
1895
    size_t index;
80✔
1896

1897
    entries = NULL;
136✔
1898
    entry_count = loader_registry_get_entries(&entries);
136✔
1899

1900
    if (names != NULL && max_names > 0) {
136!
1901
        limit = entry_count;
68✔
1902
        if (limit > max_names) {
68!
1903
            limit = max_names;
1904
        }
1905
        for (index = 0; index < limit; ++index) {
308✔
1906
            names[index] = entries[index].name;
240✔
1907
        }
168✔
1908
    }
32✔
1909

1910
    return entry_count;
168✔
1911
}
32✔
1912

1913

1914
/* emacs Local Variables:      */
1915
/* emacs mode: c               */
1916
/* emacs tab-width: 4          */
1917
/* emacs indent-tabs-mode: nil */
1918
/* emacs c-basic-offset: 4     */
1919
/* emacs End:                  */
1920
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1921
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc