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

saitoha / libsixel / 24464011284

15 Apr 2026 02:59PM UTC coverage: 85.587% (+0.003%) from 85.584%
24464011284

push

github

saitoha
ci(pcc): add shared libs and amalgamation matrix jobs

102373 of 214266 branches covered (47.78%)

124800 of 145817 relevant lines covered (85.59%)

16908164.98 hits per line

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

87.84
/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-order-schema.h"
106
#include "loader-quicklook.h"
107
#include "loader-registry.h"
108
#include "loader-factory.h"
109
#include "loader-manager.h"
110
#include "loader-component.h"
111
#include "loader-wic.h"
112
#include "compat_stub.h"
113
#include "frame.h"
114
#include "chunk.h"
115
#include "allocator.h"
116
#include "encoder.h"
117
#include "logger.h"
118
#include "options.h"
119
#include "tty.h"
120
#include "threading.h"
121
#include "sleep.h"
122
#include "sixel_atomic.h"
123

124
#ifndef STDOUT_FILENO
125
# define STDOUT_FILENO 1
126
#endif
127
#ifndef STDERR_FILENO
128
# define STDERR_FILENO 2
129
#endif
130

131
#define SIXEL_LOADER_OSC11_BG_QUERY_ENV "SIXEL_LOADER_OSC11_BG_QUERY"
132
#define SIXEL_LOADER_OSC11_BG_QUERY_TIMEOUT_ENV \
133
    "SIXEL_LOADER_OSC11_BG_QUERY_TIMEOUT_MS"
134
#define SIXEL_LOADER_OSC11_BG_QUERY_TIMEOUT_DEFAULT_MS 50
135

136
/*
137
 * Internal loader state carried across backends.  The fields mirror the
138
 * original `loader.c` layout to keep statistics, logging, and allocator
139
 * ownership centralized while implementations move into per-backend files.
140
 */
141
struct sixel_loader {
142
    sixel_atomic_u32_t ref;
143
    int fstatic;
144
    int fuse_palette;
145
    int reqcolors;
146
    unsigned char bgcolor[3];
147
    int has_bgcolor;
148
    int bgcolor_source;
149
    int loop_control;
150
    int finsecure;
151
    int has_start_frame_no;
152
    int start_frame_no;
153
    int const *cancel_flag;
154
    void *context;
155
    sixel_logger_t logger;
156
    char *loader_order;
157
    sixel_option_argument_list_resolution_t loader_order_resolution;
158
    sixel_allocator_t *allocator;
159
    char last_loader_name[64];
160
    char last_source_path[PATH_MAX];
161
    size_t last_input_bytes;
162
    int callback_failed;
163
    int log_loader_finished;
164
    char log_path[PATH_MAX];
165
    char log_loader_name[64];
166
    size_t log_input_bytes;
167
    int log_timeline_job_seq;
168
    int timeline_manager_select_job;
169
    int timeline_manager_select_open;
170
    int timeline_candidate_select_job;
171
    int timeline_candidate_select_open;
172
    char timeline_candidate_worker[96];
173
};
174

175
typedef struct sixel_loader_callback_state {
176
    sixel_loader_t *loader;
177
    sixel_load_image_function fn;
178
    void *context;
179
} sixel_loader_callback_state_t;
180

181
typedef struct sixel_loader_component_option_context {
182
    sixel_loader_t *loader;
183
    int reqcolors;
184
    sixel_loader_suboptions_t suboptions;
185
} sixel_loader_component_option_context_t;
186

187
typedef struct sixel_loader_manager_trace_context {
188
    sixel_loader_t *loader;
189
    size_t input_bytes;
190
    int current_select_job;
191
    char current_worker[96];
192
} sixel_loader_manager_trace_context_t;
193

194
typedef struct sixel_loader_osc11_bg_query_job {
195
    sixel_thread_t thread;
196
    sixel_atomic_u32_t finished;
197
    sixel_atomic_u32_t stop_requested;
198
    unsigned char bgcolor[3];
199
    SIXELSTATUS status;
200
    int timeout_ms;
201
    int started;
202
} sixel_loader_osc11_bg_query_job_t;
203

204
static int
205
loader_osc11_bg_query_thread_main(void *context);
206

207
static int
208
loader_osc11_bg_query_should_stop(void *context);
209

210
static void
211
loader_osc11_bg_query_job_init(sixel_loader_osc11_bg_query_job_t *job);
212

213
static int
214
loader_osc11_bg_query_job_is_finished(void *context);
215

216
static int
217
loader_osc11_bg_query_job_apply_if_ready(
218
    sixel_loader_t *loader,
219
    sixel_loader_osc11_bg_query_job_t *job);
220

221
static void
222
loader_osc11_bg_query_job_join(sixel_loader_osc11_bg_query_job_t *job);
223

224
static int
225
loader_can_query_osc11_bgcolor(sixel_loader_t const *loader);
226

227
int
228
sixel_loader_callback_is_canceled(void *data)
79,368✔
229
{
230
    sixel_loader_callback_state_t *state;
43,722✔
231
    void *actual_context;
43,722✔
232

233
    actual_context = loader_timeline_unwrap_callback_context(data);
79,368✔
234
    state = (sixel_loader_callback_state_t *)actual_context;
79,368✔
235
    if (state == NULL || state->loader == NULL ||
79,368!
236
        state->loader->cancel_flag == NULL) {
72,898!
237
        return 0;
9,062✔
238
    }
239

240
    return *state->loader->cancel_flag != 0;
69,473✔
241
}
52,216✔
242

243

244
#if HAVE_POSIX_SPAWNP
245
extern char **environ;
246
#endif
247

248
static char *
249
loader_strdup(char const *text, sixel_allocator_t *allocator)
45,127✔
250
{
251
    char *copy;
24,114✔
252
    size_t length;
24,114✔
253

254
    if (text == NULL) {
45,127!
255
        return NULL;
256
    }
257

258
    length = strlen(text) + 1;
45,127✔
259
    copy = (char *)sixel_allocator_malloc(allocator, length);
45,127✔
260
    if (copy == NULL) {
45,127!
261
        return NULL;
262
    }
263

264
    /* Copy the terminating NUL byte as part of length. */
265
    memcpy(copy, text, length);
45,127✔
266

267
    return copy;
45,127✔
268
}
23,682✔
269

270
int
271
sixel_loader_is_osc11_bg_query_enabled(char const *value)
102,160✔
272
{
273
    if (value == NULL) {
102,160✔
274
        return 0;
38,102✔
275
    }
276

277
    if (strcmp(value, "1") == 0) {
50,994!
278
        return 1;
50,994✔
279
    }
280

281
    return 0;
282
}
51,076✔
283

284
int
285
sixel_loader_parse_osc11_bg_query_timeout_ms(char const *value)
104,643✔
286
{
287
    long parsed;
55,466✔
288
    char *endptr;
55,466✔
289

290
    parsed = 0L;
104,643✔
291
    endptr = NULL;
104,643✔
292

293
    if (value == NULL || value[0] == '\0') {
104,643!
294
        return SIXEL_LOADER_OSC11_BG_QUERY_TIMEOUT_DEFAULT_MS;
78,461✔
295
    }
296

297
    errno = 0;
×
298
    parsed = strtol(value, &endptr, 10);
×
299
    if (endptr == value || *endptr != '\0' ||
×
300
            errno == ERANGE || parsed < 0L ||
×
301
            parsed > (long)INT_MAX) {
×
302
        return SIXEL_LOADER_OSC11_BG_QUERY_TIMEOUT_DEFAULT_MS;
303
    }
304

305
    return (int)parsed;
306
}
52,383✔
307

308
int
309
sixel_loader_wait_for_condition(sixel_loader_wait_predicate_t predicate,
1,303✔
310
                                void *context,
311
                                int timeout_ms)
312
{
313
    int elapsed;
572✔
314

315
    elapsed = 0;
1,303✔
316

317
    if (predicate == NULL) {
1,303!
318
        return 0;
319
    }
320
    if (timeout_ms < 0) {
1,303!
321
        timeout_ms = 0;
322
    }
323

324
    if (predicate(context)) {
1,303!
325
        return 1;
1✔
326
    }
327

328
    /*
329
     * Keep polling intervals short so the loader can continue as soon as the
330
     * background query completes, while still bounding total wait time.
331
     */
332
    while (elapsed < timeout_ms) {
1,364!
333
        sixel_sleep(1000u);
1,364✔
334
        if (predicate(context)) {
1,364!
335
            return 1;
730✔
336
        }
337
        ++elapsed;
62✔
338
    }
339

340
    return predicate(context);
×
341
}
208✔
342

343
static int
344
loader_osc11_bg_query_thread_main(void *context)
2,004✔
345
{
346
    sixel_loader_osc11_bg_query_job_t *job;
890✔
347

348
    job = (sixel_loader_osc11_bg_query_job_t *)context;
2,004✔
349
    if (job == NULL) {
2,004!
350
        return SIXEL_BAD_ARGUMENT;
351
    }
352

353
    job->status = sixel_tty_query_osc11_bgcolor_with_drain(
3,783✔
354
        job->bgcolor,
2,004✔
355
        job->timeout_ms,
225✔
356
        loader_osc11_bg_query_should_stop,
357
        job);
225✔
358
    sixel_fence_release();
2,004✔
359
    (void)sixel_atomic_fetch_add_u32(&job->finished, 1u);
2,004✔
360

361
    return job->status;
2,004✔
362
}
225✔
363

364
static int
365
loader_osc11_bg_query_should_stop(void *context)
366
{
367
    sixel_loader_osc11_bg_query_job_t *job;
368
    unsigned int stop_requested;
369

370
    job = (sixel_loader_osc11_bg_query_job_t *)context;
×
371
    stop_requested = 0u;
×
372
    if (job == NULL) {
×
373
        return 1;
374
    }
375

376
    stop_requested = sixel_atomic_fetch_add_u32(&job->stop_requested, 0u);
×
377
    if (stop_requested != 0u) {
×
378
        return 1;
379
    }
380

381
    return 0;
382
}
383

384
static void
385
loader_osc11_bg_query_job_init(sixel_loader_osc11_bg_query_job_t *job)
104,643✔
386
{
387
    if (job == NULL) {
104,643!
388
        return;
389
    }
390

391
    job->finished = 0u;
104,643✔
392
    job->stop_requested = 0u;
104,643✔
393
    job->bgcolor[0] = 0u;
104,643✔
394
    job->bgcolor[1] = 0u;
104,643✔
395
    job->bgcolor[2] = 0u;
104,643✔
396
    job->status = SIXEL_FALSE;
104,643✔
397
    job->timeout_ms = SIXEL_LOADER_OSC11_BG_QUERY_TIMEOUT_DEFAULT_MS;
104,643✔
398
    job->started = 0;
104,643✔
399
}
52,383✔
400

401
static int
402
loader_osc11_bg_query_job_is_finished(void *context)
6,639✔
403
{
404
    sixel_loader_osc11_bg_query_job_t *job;
2,947✔
405
    unsigned int finished;
2,947✔
406

407
    job = (sixel_loader_osc11_bg_query_job_t *)context;
6,639✔
408
    finished = 0u;
6,639✔
409

410
    if (job == NULL || job->started == 0) {
6,639!
411
        return 0;
412
    }
413

414
    finished = sixel_atomic_fetch_add_u32(&job->finished, 0u);
6,639!
415
    if (finished == 0u) {
6,639!
416
        return 0;
1,484✔
417
    }
418

419
    sixel_fence_acquire();
3,972✔
420
    return 1;
3,972✔
421
}
871✔
422

423
static int
424
loader_osc11_bg_query_job_apply_if_ready(
1,986✔
425
    sixel_loader_t *loader,
426
    sixel_loader_osc11_bg_query_job_t *job)
427
{
428
    if (loader == NULL || job == NULL) {
1,986!
429
        return 0;
430
    }
431
    if (loader_osc11_bg_query_job_is_finished(job) == 0) {
1,986!
432
        return 0;
433
    }
434
    if (SIXEL_FAILED(job->status)) {
1,986!
435
        return 0;
1,104✔
436
    }
437

438
    loader->bgcolor[0] = job->bgcolor[0];
×
439
    loader->bgcolor[1] = job->bgcolor[1];
×
440
    loader->bgcolor[2] = job->bgcolor[2];
×
441
    loader->has_bgcolor = 1;
×
442

443
    return 1;
×
444
}
223✔
445

446
static void
447
loader_osc11_bg_query_job_join(sixel_loader_osc11_bg_query_job_t *job)
104,637✔
448
{
449
    if (job == NULL || job->started == 0) {
104,637!
450
        return;
77,347✔
451
    }
452

453
    /*
454
     * Ask the reader thread to stop only when loader teardown begins. This
455
     * keeps draining late OSC11 bytes during the active conversion window.
456
     */
457
    (void)sixel_atomic_fetch_add_u32(&job->stop_requested, 1u);
1,998✔
458
    sixel_thread_join(&job->thread);
1,998✔
459
    job->started = 0;
1,998✔
460
}
52,382✔
461

462
static int
463
loader_can_query_osc11_bgcolor(sixel_loader_t const *loader)
104,643✔
464
{
465
    char const *env_value;
55,466✔
466

467
    env_value = sixel_compat_getenv(SIXEL_LOADER_OSC11_BG_QUERY_ENV);
104,643✔
468
    if (loader == NULL || loader->has_bgcolor != 0) {
104,643!
469
        return 0;
1,895✔
470
    }
471
    if (!sixel_loader_is_osc11_bg_query_enabled(env_value)) {
102,160!
472
        return 0;
38,102✔
473
    }
474
    if (sixel_compat_isatty(STDOUT_FILENO) == 0 &&
74,245!
475
            sixel_compat_isatty(STDERR_FILENO) == 0) {
49,008!
476
        return 0;
37,350✔
477
    }
478

479
    return 1;
1,114✔
480
}
52,383✔
481

482

483

484
static int
485
loader_timeline_next_job(sixel_loader_t *loader)
506,412✔
486
{
487
    int job_id;
256,297✔
488

489
    job_id = -1;
532,594✔
490
    if (loader == NULL) {
506,412!
491
        return -1;
492
    }
493
    if (loader->log_timeline_job_seq < 0) {
532,594!
494
        loader->log_timeline_job_seq = 0;
×
495
    }
496
    job_id = loader->log_timeline_job_seq;
532,594✔
497
    loader->log_timeline_job_seq = job_id + 1;
532,594✔
498
    return job_id;
532,594✔
499
}
267,519✔
500

501
static void
502
loader_log_timeline_event(sixel_loader_t *loader,
1,065,187✔
503
                          char const *worker,
504
                          char const *role,
505
                          char const *event,
506
                          int job_id)
507
{
508
    sixel_logger_t *logger;
564,958✔
509

510
    logger = NULL;
1,065,187✔
511
    if (loader != NULL) {
1,065,187!
512
        logger = &loader->logger;
1,065,187✔
513
    }
535,037✔
514
    if (logger == NULL || !logger->active ||
1,066,437!
515
            worker == NULL || role == NULL || event == NULL || job_id < 0) {
2,750!
516
        return;
797,367✔
517
    }
518

519
    sixel_logger_logf(logger,
4,100✔
520
                      role,
1,350✔
521
                      worker,
1,350✔
522
                      event,
1,350✔
523
                      job_id,
1,350✔
524
                      -1,
525
                      0,
526
                      0,
527
                      0,
528
                      0,
529
                      "");
530
}
535,037!
531

532
static void
533
loader_timeline_select_phase_start(
427,951✔
534
    sixel_loader_t *loader,
535
    char const *worker,
536
    int *job_id_out,
537
    int *open_out)
538
{
539
    int job_id;
227,013✔
540

541
    job_id = -1;
427,951✔
542
    if (job_id_out != NULL) {
427,951!
543
        *job_id_out = -1;
427,951✔
544
    }
215,136✔
545
    if (open_out != NULL) {
427,951!
546
        *open_out = 0;
427,951✔
547
    }
215,136✔
548
    if (loader == NULL || worker == NULL || worker[0] == '\0') {
427,951!
549
        return;
550
    }
551

552
    job_id = loader_timeline_next_job(loader);
427,951!
553
    if (job_id < 0) {
427,951!
554
        return;
555
    }
556

557
    loader_log_timeline_event(loader,
643,087✔
558
                              worker,
215,136✔
559
                              "loader/select",
560
                              "start",
561
                              job_id);
215,136✔
562
    if (job_id_out != NULL) {
427,951!
563
        *job_id_out = job_id;
427,951✔
564
    }
215,136✔
565
    if (open_out != NULL) {
427,951!
566
        *open_out = 1;
427,951✔
567
    }
215,136✔
568
}
215,136!
569

570
static void
571
loader_timeline_select_phase_finish(
430,671✔
572
    sixel_loader_t *loader,
573
    char const *worker,
574
    int *job_id_io,
575
    int *open_io,
576
    char const *event)
577
{
578
    int job_id;
228,481✔
579

580
    job_id = -1;
430,671✔
581
    if (loader == NULL || worker == NULL || worker[0] == '\0' ||
631,840!
582
            job_id_io == NULL || open_io == NULL ||
430,671!
583
            event == NULL || *open_io == 0) {
430,671!
584
        return;
2,140✔
585
    }
586

587
    job_id = *job_id_io;
427,951✔
588
    if (job_id >= 0) {
427,951!
589
        loader_log_timeline_event(loader,
643,087✔
590
                                  worker,
215,136✔
591
                                  "loader/select",
592
                                  event,
215,136✔
593
                                  job_id);
215,136✔
594
    }
215,136✔
595
    *job_id_io = -1;
427,951✔
596
    *open_io = 0;
427,951✔
597
}
216,728!
598

599
static void
600
loader_timeline_select_suspend_for_callback(
109,355✔
601
    sixel_loader_t *loader,
602
    int *resume_manager,
603
    int *resume_candidate)
604
{
605
    if (resume_manager != NULL) {
109,355!
606
        *resume_manager = 0;
109,355✔
607
    }
55,179✔
608
    if (resume_candidate != NULL) {
109,355!
609
        *resume_candidate = 0;
109,355✔
610
    }
55,179✔
611
    if (loader == NULL) {
109,355✔
612
        return;
613
    }
614

615
    if (loader->timeline_candidate_select_open != 0 &&
109,355!
616
            loader->timeline_candidate_worker[0] != '\0') {
109,355!
617
        loader_timeline_select_phase_finish(
109,355✔
618
            loader,
55,179✔
619
            loader->timeline_candidate_worker,
109,355✔
620
            &loader->timeline_candidate_select_job,
55,179✔
621
            &loader->timeline_candidate_select_open,
55,179✔
622
            "finish");
623
        if (resume_candidate != NULL) {
109,355!
624
            *resume_candidate = 1;
109,355✔
625
        }
55,179✔
626
    }
55,179✔
627
    if (loader->timeline_manager_select_open != 0) {
109,446!
628
        loader_timeline_select_phase_finish(
108,175✔
629
            loader,
54,269✔
630
            "loader/manager",
631
            &loader->timeline_manager_select_job,
54,269✔
632
            &loader->timeline_manager_select_open,
54,269✔
633
            "finish");
634
        if (resume_manager != NULL) {
108,175!
635
            *resume_manager = 1;
108,175✔
636
        }
54,269✔
637
    }
54,269✔
638
}
55,179✔
639

640
static void
641
loader_timeline_select_emit_immediate_failure(
2,720✔
642
    sixel_loader_t *loader,
643
    char const *worker)
644
{
645
    int job_id;
1,468✔
646
    int open_flag;
1,468✔
647

648
    job_id = -1;
2,720✔
649
    open_flag = 0;
2,720✔
650
    if (loader == NULL || worker == NULL || worker[0] == '\0') {
2,720!
651
        return;
×
652
    }
653

654
    loader_timeline_select_phase_start(loader,
4,312✔
655
                                       worker,
1,592✔
656
                                       &job_id,
657
                                       &open_flag);
658
    loader_timeline_select_phase_finish(loader,
4,312✔
659
                                        worker,
1,592✔
660
                                        &job_id,
661
                                        &open_flag,
662
                                        "fail");
663
}
1,592!
664

665
static void
666
loader_timeline_select_resume_after_callback(
109,349✔
667
    sixel_loader_t *loader,
668
    int resume_manager,
669
    int resume_candidate,
670
    SIXELSTATUS callback_status)
671
{
672
    if (loader == NULL) {
109,349✔
673
        return;
674
    }
675

676
    if (SIXEL_FAILED(callback_status)) {
109,349✔
677
        if (resume_candidate != 0 &&
1,950!
678
                loader->timeline_candidate_worker[0] != '\0') {
1,950!
679
            loader_timeline_select_emit_immediate_failure(
1,950✔
680
                loader,
1,251✔
681
                loader->timeline_candidate_worker);
1,950✔
682
        }
1,251✔
683
        if (resume_manager != 0) {
2,041!
684
            loader_timeline_select_emit_immediate_failure(
770✔
685
                loader,
341✔
686
                "loader/manager");
687
        }
341✔
688
        return;
1,950✔
689
    }
690

691
    if (resume_manager != 0) {
107,399!
692
        loader_timeline_select_phase_start(
107,399✔
693
            loader,
53,927✔
694
            "loader/manager",
695
            &loader->timeline_manager_select_job,
53,927✔
696
            &loader->timeline_manager_select_open);
53,927✔
697
    }
53,927✔
698
    if (resume_candidate != 0 &&
107,399!
699
            loader->timeline_candidate_worker[0] != '\0') {
107,399!
700
        loader_timeline_select_phase_start(
107,399✔
701
            loader,
53,927✔
702
            loader->timeline_candidate_worker,
107,399✔
703
            &loader->timeline_candidate_select_job,
53,927✔
704
            &loader->timeline_candidate_select_open);
53,927✔
705
    }
53,927✔
706
}
55,178✔
707

708
static SIXELSTATUS
709
loader_callback_trampoline(sixel_frame_t *frame, void *data)
109,356✔
710
{
711
    sixel_loader_callback_state_t *state;
58,052✔
712
    SIXELSTATUS status;
58,052✔
713
    int resume_manager_select;
58,052✔
714
    int resume_candidate_select;
58,052✔
715

716
    state = (sixel_loader_callback_state_t *)data;
109,356✔
717
    resume_manager_select = 0;
109,356✔
718
    resume_candidate_select = 0;
109,356✔
719
    if (state == NULL || state->fn == NULL) {
109,356!
720
        return SIXEL_BAD_ARGUMENT;
2✔
721
    }
722

723
    if (state->loader != NULL) {
109,356!
724
        /*
725
         * loader/select represents loader-side work only. Suspend these spans
726
         * while control is in the downstream frame callback.
727
         */
728
        loader_timeline_select_suspend_for_callback(state->loader,
109,355✔
729
                                                    &resume_manager_select,
730
                                                    &resume_candidate_select);
731
    }
55,179✔
732
    status = state->fn(frame, state->context);
109,356✔
733
    if (state->loader != NULL) {
109,351!
734
        loader_timeline_select_resume_after_callback(state->loader,
164,527✔
735
                                                     resume_manager_select,
55,178✔
736
                                                     resume_candidate_select,
55,178✔
737
                                                     status);
55,178✔
738
    }
55,178✔
739
    if (SIXEL_FAILED(status) && state->loader != NULL) {
114,601!
740
        state->loader->callback_failed = 1;
1,950✔
741
    }
1,251✔
742

743
    return status;
82,168✔
744
}
55,178✔
745

746

747
static SIXELSTATUS
748
loader_apply_component_options(sixel_loader_component_t *component,
105,930✔
749
                               sixel_loader_t const *loader,
750
                               int reqcolors,
751
                               sixel_loader_suboptions_t const *suboptions)
752
{
753
    typedef struct loader_component_option_entry {
26,348✔
754
        int option;
755
        char const *name;
756
    } loader_component_option_entry_t;
757

758
    loader_component_option_entry_t const options[] = {
105,930✔
759
        { SIXEL_LOADER_OPTION_REQUIRE_STATIC, "require-static" },
760
        { SIXEL_LOADER_OPTION_USE_PALETTE, "use-palette" },
761
        { SIXEL_LOADER_OPTION_REQCOLORS, "reqcolors" },
762
        { SIXEL_LOADER_OPTION_BGCOLOR, "bgcolor" },
763
        { SIXEL_LOADER_OPTION_LOOP_CONTROL, "loop-control" },
764
        { SIXEL_LOADER_OPTION_START_FRAME_NO, "start-frame-no" }
765
    };
766
    void const *value;
56,190✔
767
    char message[128];
56,190✔
768
    size_t index;
56,190✔
769
    SIXELSTATUS status;
56,190✔
770
    int suboption_value;
56,190✔
771
    char const *component_name;
56,190✔
772

773
    /*
774
     * Distribute common execution parameters to every loader component.
775
     *
776
     * +---------------------------+-------------------------------+
777
     * | option                    | value source                  |
778
     * +---------------------------+-------------------------------+
779
     * | REQUIRE_STATIC            | loader->fstatic               |
780
     * | USE_PALETTE               | loader->fuse_palette          |
781
     * | REQCOLORS                 | normalized reqcolors          |
782
     * | BGCOLOR                   | loader->bgcolor or NULL       |
783
     * | LOOP_CONTROL              | loader->loop_control          |
784
     * | START_FRAME_NO            | loader->start_frame_no/NULL   |
785
     * +---------------------------+-------------------------------+
786
     */
787
    status = SIXEL_OK;
105,930✔
788
    message[0] = '\0';
105,930✔
789
    index = 0;
105,930✔
790
    value = NULL;
105,930✔
791
    suboption_value = 0;
105,930✔
792
    component_name = NULL;
105,930✔
793
    if (component == NULL || loader == NULL) {
105,930!
794
        return SIXEL_BAD_ARGUMENT;
795
    }
796
    component_name = sixel_loader_component_get_name(component);
105,930✔
797

798
    for (index = 0; index < sizeof(options) / sizeof(options[0]); ++index) {
767,858!
799
        switch (options[index].option) {
635,580!
800
        case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
52,556!
801
            value = &loader->fstatic;
105,930✔
802
            break;
105,930✔
803
        case SIXEL_LOADER_OPTION_USE_PALETTE:
52,556!
804
            value = &loader->fuse_palette;
105,930✔
805
            break;
105,930✔
806
        case SIXEL_LOADER_OPTION_REQCOLORS:
26,208!
807
            value = &reqcolors;
79,582✔
808
            break;
79,582✔
809
        case SIXEL_LOADER_OPTION_BGCOLOR:
52,556!
810
            value = loader->has_bgcolor ? loader->bgcolor : NULL;
105,930✔
811
            break;
79,582✔
812
        case SIXEL_LOADER_OPTION_LOOP_CONTROL:
52,556!
813
            value = &loader->loop_control;
105,930✔
814
            break;
105,930✔
815
        case SIXEL_LOADER_OPTION_START_FRAME_NO:
52,556!
816
            value = loader->has_start_frame_no
94,463✔
817
                ? &loader->start_frame_no : NULL;
53,892✔
818
            break;
79,582✔
819
        default:
×
820
            value = NULL;
51,564✔
821
            break;
822
        }
823

824
        status = sixel_loader_component_setopt(component,
955,824✔
825
                                               options[index].option,
477,492✔
826
                                               value);
320,244✔
827
        if (SIXEL_FAILED(status)) {
635,580!
828
            (void)sixel_compat_snprintf(message,
×
829
                                        sizeof(message),
830
                                        "sixel_loader_load_file: "
831
                                        "failed to apply loader option "
832
                                        "'%s'.",
833
                                        options[index].name);
×
834
            sixel_helper_set_additional_message(
×
835
                message);
836
            return status;
×
837
        }
838
    }
320,244✔
839

840
    if (suboptions == NULL) {
105,930!
841
        status = sixel_loader_component_setopt(
×
842
            component,
843
            SIXEL_LOADER_COMPONENT_OPTION_BGCOLOR_SOURCE,
844
            &loader->bgcolor_source);
×
845
        if (SIXEL_FAILED(status)) {
×
846
            sixel_helper_set_additional_message(
×
847
                "sixel_loader_load_file: failed to apply loader option "
848
                "'bgcolor-source'.");
849
            return status;
×
850
        }
851
        return SIXEL_OK;
852
    }
853

854
#if HAVE_WIC
855
    suboption_value = suboptions->wic_ico_minsize;
38,204✔
856
    status = sixel_loader_component_setopt(
38,204✔
857
        component,
3,825✔
858
        SIXEL_LOADER_COMPONENT_OPTION_WIC_ICO_MINSIZE,
859
        &suboption_value);
860
    if (SIXEL_FAILED(status)) {
38,204✔
861
        sixel_helper_set_additional_message(
862
            "sixel_loader_load_file: failed to apply loader option "
863
            "'wic-ico-minsize'.");
864
        return status;
865
    }
866
#endif
867

868
    suboption_value = suboptions->libpng_enable_cms;
105,930✔
869
    status = sixel_loader_component_setopt(
105,930✔
870
        component,
53,374✔
871
        SIXEL_LOADER_COMPONENT_OPTION_LIBPNG_ENABLE_CMS,
872
        &suboption_value);
873
    if (SIXEL_FAILED(status)) {
105,930!
874
        sixel_helper_set_additional_message(
×
875
            "sixel_loader_load_file: failed to apply loader option "
876
            "'libpng-enable-cms'.");
877
        return status;
×
878
    }
879

880
    suboption_value = suboptions->libjpeg_enable_cms;
105,930✔
881
    status = sixel_loader_component_setopt(
105,930✔
882
        component,
53,374✔
883
        SIXEL_LOADER_COMPONENT_OPTION_LIBJPEG_ENABLE_CMS,
884
        &suboption_value);
885
    if (SIXEL_FAILED(status)) {
105,930!
886
        sixel_helper_set_additional_message(
×
887
            "sixel_loader_load_file: failed to apply loader option "
888
            "'libjpeg-enable-cms'.");
889
        return status;
×
890
    }
891

892
    suboption_value = suboptions->libwebp_enable_cms;
105,930✔
893
    status = sixel_loader_component_setopt(
105,930✔
894
        component,
53,374✔
895
        SIXEL_LOADER_COMPONENT_OPTION_LIBWEBP_ENABLE_CMS,
896
        &suboption_value);
897
    if (SIXEL_FAILED(status)) {
105,930!
898
        sixel_helper_set_additional_message(
×
899
            "sixel_loader_load_file: failed to apply loader option "
900
            "'libwebp-enable-cms'.");
901
        return status;
×
902
    }
903

904
    suboption_value = suboptions->libtiff_enable_cms;
105,930✔
905
    status = sixel_loader_component_setopt(
105,930✔
906
        component,
53,374✔
907
        SIXEL_LOADER_COMPONENT_OPTION_LIBTIFF_ENABLE_CMS,
908
        &suboption_value);
909
    if (SIXEL_FAILED(status)) {
105,930!
910
        sixel_helper_set_additional_message(
×
911
            "sixel_loader_load_file: failed to apply loader option "
912
            "'libtiff-enable-cms'.");
913
        return status;
×
914
    }
915

916
    suboption_value = suboptions->builtin_enable_cms;
105,930✔
917
    status = sixel_loader_component_setopt(
105,930✔
918
        component,
53,374✔
919
        SIXEL_LOADER_COMPONENT_OPTION_BUILTIN_ENABLE_CMS,
920
        &suboption_value);
921
    if (SIXEL_FAILED(status)) {
105,930!
922
        sixel_helper_set_additional_message(
×
923
            "sixel_loader_load_file: failed to apply loader option "
924
            "'builtin-enable-cms'.");
925
        return status;
×
926
    }
927

928
    suboption_value = suboptions->builtin_bmp_info40_mode;
105,930✔
929
    status = sixel_loader_component_setopt(
105,930✔
930
        component,
53,374✔
931
        SIXEL_LOADER_COMPONENT_OPTION_BUILTIN_BMP_INFO40_MODE,
932
        &suboption_value);
933
    if (SIXEL_FAILED(status)) {
105,930!
934
        sixel_helper_set_additional_message(
×
935
            "sixel_loader_load_file: failed to apply loader option "
936
            "'builtin-bmp-info40-mode'.");
937
        return status;
×
938
    }
939

940
    suboption_value = suboptions->libjpeg_enable_orientation;
105,930✔
941
    status = sixel_loader_component_setopt(
105,930✔
942
        component,
53,374✔
943
        SIXEL_LOADER_COMPONENT_OPTION_LIBJPEG_ENABLE_ORIENTATION,
944
        &suboption_value);
945
    if (SIXEL_FAILED(status)) {
105,930!
946
        sixel_helper_set_additional_message(
×
947
            "sixel_loader_load_file: failed to apply loader option "
948
            "'libjpeg-enable-orientation'.");
949
        return status;
×
950
    }
951

952
    suboption_value = suboptions->libpng_enable_orientation;
105,930✔
953
    status = sixel_loader_component_setopt(
105,930✔
954
        component,
53,374✔
955
        SIXEL_LOADER_COMPONENT_OPTION_LIBPNG_ENABLE_ORIENTATION,
956
        &suboption_value);
957
    if (SIXEL_FAILED(status)) {
105,930!
958
        sixel_helper_set_additional_message(
×
959
            "sixel_loader_load_file: failed to apply loader option "
960
            "'libpng-enable-orientation'.");
961
        return status;
×
962
    }
963

964
    suboption_value = suboptions->libwebp_enable_orientation;
105,930✔
965
    status = sixel_loader_component_setopt(
105,930✔
966
        component,
53,374✔
967
        SIXEL_LOADER_COMPONENT_OPTION_LIBWEBP_ENABLE_ORIENTATION,
968
        &suboption_value);
969
    if (SIXEL_FAILED(status)) {
105,930!
970
        sixel_helper_set_additional_message(
×
971
            "sixel_loader_load_file: failed to apply loader option "
972
            "'libwebp-enable-orientation'.");
973
        return status;
×
974
    }
975

976
    suboption_value = suboptions->coregraphics_enable_orientation;
105,930✔
977
    status = sixel_loader_component_setopt(
105,930✔
978
        component,
53,374✔
979
        SIXEL_LOADER_COMPONENT_OPTION_COREGRAPHICS_ENABLE_ORIENTATION,
980
        &suboption_value);
981
    if (SIXEL_FAILED(status)) {
105,930!
982
        sixel_helper_set_additional_message(
×
983
            "sixel_loader_load_file: failed to apply loader option "
984
            "'coregraphics-enable-orientation'.");
985
        return status;
×
986
    }
987

988
    if (component_name != NULL && strcmp(component_name, "libpng") == 0) {
105,930!
989
        suboption_value = suboptions->libpng_cms_engine;
3,495✔
990
    } else if (component_name != NULL &&
105,930!
991
               strcmp(component_name, "libjpeg") == 0) {
102,435!
992
        suboption_value = suboptions->libjpeg_cms_engine;
355✔
993
    } else if (component_name != NULL &&
102,435!
994
               strcmp(component_name, "libwebp") == 0) {
102,080!
995
        suboption_value = suboptions->libwebp_cms_engine;
2,008✔
996
    } else if (component_name != NULL &&
102,080!
997
               strcmp(component_name, "libtiff") == 0) {
100,072!
998
        suboption_value = suboptions->libtiff_cms_engine;
730✔
999
    } else if (component_name != NULL &&
102,834!
1000
               (strcmp(component_name, "builtin") == 0 ||
99,342✔
1001
                strcmp(component_name, "gnome-thumbnailer") == 0)) {
3,655!
1002
        suboption_value = suboptions->builtin_cms_engine;
96,006✔
1003
    } else {
44,224✔
1004
        suboption_value = 0;
3,336✔
1005
    }
1006
    status = sixel_loader_component_setopt(
105,930✔
1007
        component,
53,374✔
1008
        SIXEL_LOADER_COMPONENT_OPTION_CMS_ENGINE,
1009
        &suboption_value);
1010
    if (SIXEL_FAILED(status)) {
105,930!
1011
        sixel_helper_set_additional_message(
×
1012
            "sixel_loader_load_file: failed to apply loader option "
1013
            "'cms-engine'.");
1014
        return status;
×
1015
    }
1016

1017
    status = sixel_loader_component_setopt(
132,278✔
1018
        component,
53,374✔
1019
        SIXEL_LOADER_COMPONENT_OPTION_BGCOLOR_SOURCE,
1020
        &loader->bgcolor_source);
105,930✔
1021
    if (SIXEL_FAILED(status)) {
105,930!
1022
        sixel_helper_set_additional_message(
×
1023
            "sixel_loader_load_file: failed to apply loader option "
1024
            "'bgcolor-source'.");
1025
        return status;
×
1026
    }
1027

1028
    return SIXEL_OK;
79,582✔
1029
}
53,374✔
1030

1031

1032
static void
1033
loader_append_chunk(char *dest,
1,585✔
1034
                    size_t capacity,
1035
                    size_t *offset,
1036
                    char const *chunk)
1037
{
1038
    size_t available;
952✔
1039
    size_t length;
952✔
1040

1041
    if (dest == NULL || offset == NULL || chunk == NULL) {
1,585!
1042
        return;
1043
    }
1044

1045
    if (*offset >= capacity) {
1,585!
1046
        return;
1047
    }
1048

1049
    available = capacity - *offset;
1,585✔
1050
    if (available == 0) {
1,585!
1051
        return;
1052
    }
1053

1054
    length = strlen(chunk);
1,585✔
1055
    if (length >= available) {
1,585!
1056
        if (available == 0) {
×
1057
            return;
1058
        }
1059
        length = available - 1u;
×
1060
    }
1061

1062
    if (length > 0) {
1,585!
1063
        memcpy(dest + *offset, chunk, length);
1,585✔
1064
        *offset += length;
1,585✔
1065
    }
1,585✔
1066

1067
    if (*offset < capacity) {
1,585!
1068
        dest[*offset] = '\0';
1,585✔
1069
    } else {
1,585✔
1070
        dest[capacity - 1u] = '\0';
×
1071
    }
1072
}
1,585!
1073

1074
static void
1075
loader_append_key_value(char *dest,
837✔
1076
                        size_t capacity,
1077
                        size_t *offset,
1078
                        char const *label,
1079
                        char const *value)
1080
{
1081
    char line[128];
504✔
1082
    int written;
504✔
1083

1084
    if (value == NULL || value[0] == '\0') {
837!
1085
        return;
×
1086
    }
1087

1088
    written = sixel_compat_snprintf(line,
1,674✔
1089
                                    sizeof(line),
1090
                                    "  %-10s: %s\n",
1091
                                    label,
837✔
1092
                                    value);
837✔
1093
    if (written < 0) {
837!
1094
        return;
1095
    }
1096

1097
    if ((size_t)written >= sizeof(line)) {
837!
1098
        line[sizeof(line) - 1u] = '\0';
×
1099
    }
1100

1101
    loader_append_chunk(dest, capacity, offset, line);
837✔
1102
}
837!
1103

1104
static void
1105
loader_extract_extension(char const *path, char *buffer, size_t capacity)
187✔
1106
{
1107
    char const *dot;
112✔
1108
    size_t index;
112✔
1109

1110
    if (buffer == NULL || capacity == 0) {
187!
1111
        return;
1112
    }
1113

1114
    buffer[0] = '\0';
187✔
1115

1116
    if (path == NULL) {
187!
1117
        return;
1118
    }
1119

1120
    dot = strrchr(path, '.');
187✔
1121
    if (dot == NULL || dot[1] == '\0') {
187!
1122
        return;
54✔
1123
    }
1124

1125
#if defined(_WIN32)
1126
    {
1127
        char const *slash;
1128
        char const *backslash;
1129

1130
        slash = strrchr(path, '/');
1131
        backslash = strrchr(path, '\\');
1132
        if ((slash != NULL && dot < slash) ||
1133
                (backslash != NULL && dot < backslash)) {
1134
            return;
1135
        }
1136
    }
1137
#else
1138
    {
1139
        char const *slash;
80✔
1140

1141
        slash = strrchr(path, '/');
133✔
1142
        if (slash != NULL && dot < slash) {
133!
1143
            return;
1144
        }
1145
    }
80!
1146
#endif
1147

1148
    if (dot[1] == '\0') {
133!
1149
        return;
1150
    }
1151

1152
    dot += 1;
133✔
1153

1154
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
554!
1155
        buffer[index] = (char)tolower((unsigned char)dot[index]);
421✔
1156
    }
421✔
1157
    buffer[index] = '\0';
133✔
1158
}
187!
1159

1160

1161

1162

1163

1164

1165

1166

1167

1168

1169

1170
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1171
static void
1172
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
330✔
1173
{
1174
    if (buffer == NULL || capacity == 0) {
330!
1175
        return;
1176
    }
1177

1178
    buffer[0] = '\0';
330✔
1179
    if (source == NULL) {
330✔
1180
        return;
1181
    }
1182

1183
    if (!CFStringGetCString(source,
660!
1184
                             buffer,
330✔
1185
                             (CFIndex)capacity,
330✔
1186
                             kCFStringEncodingUTF8)) {
1187
        buffer[0] = '\0';
1188
    }
1189
}
330✔
1190
#endif
1191

1192

1193
static void
1194
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
187✔
1195
                          char const *filename)
1196
{
1197
    enum { description_length = 128 };
1198
    enum { uttype_length = 128 };
1199
    enum { extension_length = 32 };
1200
    enum { message_length = 768 };
1201
    char message[message_length];
112✔
1202
    char type_value[description_length];
112✔
1203
    char extension_text[extension_length + 2];
112✔
1204
    char uttype[uttype_length];
112✔
1205
    char desc_buffer[description_length];
112✔
1206
    char extension[extension_length];
112✔
1207
    char const *path;
112✔
1208
    char const *display_path;
112✔
1209
    char const *metadata_path;
112✔
1210
    char const *description_text;
112✔
1211
    char *mime_string;
112✔
1212
    char *description_string;
112✔
1213
    size_t offset;
112✔
1214
    int gnome_available;
112✔
1215
    int gnome_has_dirs;
112✔
1216
    int gnome_has_match;
112✔
1217
    int suggestions;
112✔
1218

1219
    message[0] = '\0';
187✔
1220
    type_value[0] = '\0';
187✔
1221
    extension_text[0] = '\0';
187✔
1222
    uttype[0] = '\0';
187✔
1223
    desc_buffer[0] = '\0';
187✔
1224
    extension[0] = '\0';
187✔
1225
    path = NULL;
187✔
1226
    display_path = "(stdin)";
187✔
1227
    metadata_path = NULL;
187✔
1228
    description_text = NULL;
187✔
1229
    mime_string = NULL;
187✔
1230
    description_string = NULL;
187✔
1231
    offset = 0u;
187✔
1232
    gnome_available = 0;
187✔
1233
    gnome_has_dirs = 0;
187✔
1234
    gnome_has_match = 0;
187✔
1235
    suggestions = 0;
187✔
1236

1237
    if (pchunk != NULL && pchunk->source_path != NULL) {
187!
1238
        path = pchunk->source_path;
165✔
1239
    } else if (filename != NULL) {
187!
1240
        path = filename;
22✔
1241
    }
22✔
1242

1243
    if (path != NULL && strcmp(path, "-") != 0) {
187!
1244
        display_path = path;
165✔
1245
    }
165✔
1246

1247
    if (path != NULL && strcmp(path, "-") != 0 &&
187!
1248
            strstr(path, "://") == NULL) {
165!
1249
        metadata_path = path;
165✔
1250
    }
165✔
1251

1252
    loader_extract_extension(path, extension, sizeof(extension));
186✔
1253

1254
#if HAVE_FREEDESKTOP_THUMBNAILING
1255
    if (metadata_path != NULL) {
186!
1256
        /*
1257
         * Collect MIME metadata via file(1) when fork() and friends are
1258
         * available.  Windows builds compiled with clang64 lack these
1259
         * interfaces, so the thumbnail helpers remain disabled there.
1260
         */
1261
        mime_string = thumbnailer_guess_content_type(metadata_path);
165✔
1262
        description_string = thumbnailer_run_file(metadata_path, NULL);
165✔
1263
    }
165✔
1264
#else
1265
    (void)metadata_path;
1266
#endif
1267

1268
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1269
#if defined(__clang__)
1270
    /*
1271
     * Allow use of legacy UTType C APIs when compiling with the
1272
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
1273
     * so we must intentionally silence the deprecation warnings here.
1274
     */
1275
#pragma clang diagnostic push
1276
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
1277
#endif
1278
    {
1279
        CFStringRef uti_ref;
112✔
1280
        CFStringRef mime_ref;
112✔
1281
        CFStringRef ext_ref;
112✔
1282
        CFStringRef desc_ref;
112✔
1283
        CFStringRef preferred_mime;
112✔
1284
        char uti_local[uttype_length];
112✔
1285
        char desc_local[description_length];
112✔
1286
        char mime_local[64];
112✔
1287

1288
        uti_ref = NULL;
186✔
1289
        mime_ref = NULL;
186✔
1290
        ext_ref = NULL;
186✔
1291
        desc_ref = NULL;
186✔
1292
        preferred_mime = NULL;
186✔
1293
        uti_local[0] = '\0';
186✔
1294
        desc_local[0] = '\0';
186✔
1295
        mime_local[0] = '\0';
186✔
1296

1297
        if (mime_string != NULL) {
186✔
1298
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
330✔
1299
                                                 mime_string,
165✔
1300
                                                 kCFStringEncodingUTF8);
1301
        }
165✔
1302
        if (mime_ref != NULL) {
186✔
1303
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
165✔
1304
                kUTTagClassMIMEType,
165✔
1305
                mime_ref,
165✔
1306
                NULL);
1307
        }
165✔
1308
        if (uti_ref == NULL && extension[0] != '\0') {
203!
1309
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
1310
                                                extension,
1311
                                                kCFStringEncodingUTF8);
1312
            if (ext_ref != NULL) {
×
1313
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
1314
                    kUTTagClassFilenameExtension,
1315
                    ext_ref,
1316
                    NULL);
1317
            }
1318
        }
1319
        if (uti_ref != NULL) {
186✔
1320
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
165✔
1321
            desc_ref = UTTypeCopyDescription(uti_ref);
165✔
1322
            if (desc_ref != NULL) {
165!
1323
                loader_copy_cfstring(desc_ref,
330✔
1324
                                     desc_local,
165✔
1325
                                     sizeof(desc_local));
1326
                CFRelease(desc_ref);
165✔
1327
                desc_ref = NULL;
165✔
1328
            }
165✔
1329
            if (mime_string == NULL) {
183!
1330
                preferred_mime = UTTypeCopyPreferredTagWithClass(
1331
                    uti_ref,
1332
                    kUTTagClassMIMEType);
1333
                if (preferred_mime != NULL) {
×
1334
                    loader_copy_cfstring(preferred_mime,
1335
                                         mime_local,
1336
                                         sizeof(mime_local));
1337
                    CFRelease(preferred_mime);
1338
                    preferred_mime = NULL;
1339
                }
1340
                if (mime_local[0] != '\0') {
×
1341
                    mime_string = thumbnailer_strdup(mime_local);
1342
                }
1343
            }
1344
        }
165✔
1345
        if (mime_ref != NULL) {
186✔
1346
            CFRelease(mime_ref);
165✔
1347
        }
165✔
1348
        if (ext_ref != NULL) {
204!
1349
            CFRelease(ext_ref);
1350
        }
1351
        if (uti_ref != NULL) {
186✔
1352
            CFRelease(uti_ref);
165✔
1353
        }
165✔
1354
        if (uti_local[0] != '\0') {
186✔
1355
            sixel_compat_snprintf(uttype,
330✔
1356
                                  sizeof(uttype),
1357
                                  "%s",
1358
                                  uti_local);
165✔
1359
        }
165✔
1360
        if (desc_local[0] != '\0') {
186✔
1361
            sixel_compat_snprintf(desc_buffer,
330✔
1362
                                  sizeof(desc_buffer),
1363
                                  "%s",
1364
                                  desc_local);
165✔
1365
        }
165✔
1366
    }
112✔
1367
#if defined(__clang__)
1368
#pragma clang diagnostic pop
1369
#endif
1370
#endif
1371

1372
    if (description_string != NULL && description_string[0] != '\0') {
186!
1373
        description_text = description_string;
165✔
1374
    } else if (desc_buffer[0] != '\0') {
187!
1375
        description_text = desc_buffer;
1376
    } else {
1377
        description_text = "unknown content";
22✔
1378
    }
1379

1380
    sixel_compat_snprintf(type_value,
374✔
1381
                          sizeof(type_value),
1382
                          "%s",
1383
                          description_text);
187✔
1384

1385
    loader_append_chunk(message,
187✔
1386
                        sizeof(message),
1387
                        &offset,
1388
                        "diagnostic:\n");
1389
    loader_append_key_value(message,
374✔
1390
                            sizeof(message),
1391
                            &offset,
1392
                            "file",
1393
                            display_path);
187✔
1394
    loader_append_key_value(message,
374✔
1395
                            sizeof(message),
1396
                            &offset,
1397
                            "type",
1398
                            type_value);
187✔
1399

1400
    if (mime_string != NULL && mime_string[0] != '\0') {
187!
1401
        loader_append_key_value(message,
330✔
1402
                                sizeof(message),
1403
                                &offset,
1404
                                "mime",
1405
                                mime_string);
165✔
1406
    }
165✔
1407

1408
    if (uttype[0] != '\0') {
186!
1409
        loader_append_key_value(message,
330✔
1410
                                sizeof(message),
1411
                                &offset,
1412
                                "uti",
1413
                                uttype);
165✔
1414
    }
165✔
1415

1416
    if (extension[0] != '\0') {
190!
1417
        sixel_compat_snprintf(extension_text,
266✔
1418
                              sizeof(extension_text),
1419
                              ".%s",
1420
                              extension);
133✔
1421
        loader_append_key_value(message,
266✔
1422
                                sizeof(message),
1423
                                &offset,
1424
                                "extension",
1425
                                extension_text);
133✔
1426
    }
133✔
1427

1428
    loader_append_chunk(message,
187✔
1429
                        sizeof(message),
1430
                        &offset,
1431
                        "  suggestions:\n");
1432

1433
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1434
    int quicklook_available;
112✔
1435
    int quicklook_supported;
112✔
1436

1437
    quicklook_available = 0;
187✔
1438
    quicklook_supported = 0;
187✔
1439

1440
    quicklook_available = loader_registry_entry_available("quicklook");
187✔
1441
    if (quicklook_available) {
187!
1442
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
187✔
1443
    }
187✔
1444
    if (quicklook_supported) {
203✔
1445
        loader_append_chunk(message,
31✔
1446
                            sizeof(message),
1447
                            &offset,
1448
                            "    - QuickLook rendered a preview during "
1449
                            "the probe; try -L quicklook.\n");
1450
        suggestions += 1;
31✔
1451
    }
31✔
1452
#endif
1453

1454
#if HAVE_FREEDESKTOP_THUMBNAILING
1455
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
187✔
1456
    if (gnome_available) {
187!
1457
        loader_probe_gnome_thumbnailers(mime_string,
187✔
1458
                                        &gnome_has_dirs,
1459
                                        &gnome_has_match);
1460
        if (gnome_has_dirs && gnome_has_match) {
187!
1461
            loader_append_chunk(message,
1462
                                sizeof(message),
1463
                                &offset,
1464
                                "    - GNOME thumbnailer definitions match "
1465
                                "this MIME type; try -L gnome-thumbnailer.\n"
1466
                                );
1467
            suggestions += 1;
1468
        }
1469
    }
187✔
1470
#else
1471
    (void)gnome_available;
1472
    (void)gnome_has_dirs;
1473
    (void)gnome_has_match;
1474
#endif
1475

1476
    if (suggestions == 0) {
190!
1477
        loader_append_chunk(message,
156✔
1478
                            sizeof(message),
1479
                            &offset,
1480
                            "    (no thumbnail helper hints)\n");
1481
    }
156✔
1482

1483
    if (suggestions > 0) {
187!
1484
        loader_append_chunk(message,
31✔
1485
                            sizeof(message),
1486
                            &offset,
1487
                            "  hint       : Enable one of the suggested "
1488
                            "loaders with -L.\n");
1489
    } else {
31✔
1490
        loader_append_chunk(message,
156✔
1491
                            sizeof(message),
1492
                            &offset,
1493
                            "  hint       : Convert the file to PNG or "
1494
                            "enable optional loaders.\n");
1495
    }
1496

1497
    sixel_helper_set_additional_message(message);
187✔
1498

1499
    free(mime_string);
187✔
1500
    free(description_string);
187✔
1501
}
187✔
1502

1503
SIXELAPI SIXELSTATUS
1504
sixel_loader_new(
104,643✔
1505
    sixel_loader_t   /* out */ **pploader,
1506
    sixel_allocator_t/* in */  *allocator)
1507
{
1508
    SIXELSTATUS status = SIXEL_FALSE;
104,643✔
1509
    sixel_loader_t *loader;
55,466✔
1510
    sixel_allocator_t *local_allocator;
55,466✔
1511

1512
    loader = NULL;
104,643✔
1513
    local_allocator = allocator;
104,643✔
1514

1515
    if (pploader == NULL) {
104,643!
1516
        sixel_helper_set_additional_message(
×
1517
            "sixel_loader_new: pploader is null.");
1518
        status = SIXEL_BAD_ARGUMENT;
×
1519
        goto end;
×
1520
    }
1521

1522
    if (local_allocator == NULL) {
104,643✔
1523
        status = sixel_allocator_new(&local_allocator,
2,994✔
1524
                                     NULL,
1525
                                     NULL,
1526
                                     NULL,
1527
                                     NULL);
1528
        if (SIXEL_FAILED(status)) {
2,994!
1529
            goto end;
×
1530
        }
1531
    } else {
1,430✔
1532
        sixel_allocator_ref(local_allocator);
101,649✔
1533
    }
1534

1535
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
104,643✔
1536
                                                      sizeof(*loader));
1537
    if (loader == NULL) {
104,643!
1538
        sixel_helper_set_additional_message(
×
1539
            "sixel_loader_new: sixel_allocator_malloc() failed.");
1540
        status = SIXEL_BAD_ALLOCATION;
×
1541
        sixel_allocator_unref(local_allocator);
×
1542
        goto end;
×
1543
    }
1544

1545
    loader->ref = 1U;
104,643✔
1546
    loader->fstatic = 0;
104,643✔
1547
    /*
1548
     * Default policy: keep source palettes when a loader can provide them.
1549
     * This avoids unnecessary requantization loss in downstream encoding.
1550
     */
1551
    loader->fuse_palette = 1;
104,643✔
1552
    loader->reqcolors = SIXEL_PALETTE_MAX;
104,643✔
1553
    loader->bgcolor[0] = 0;
104,643✔
1554
    loader->bgcolor[1] = 0;
104,643✔
1555
    loader->bgcolor[2] = 0;
104,643✔
1556
    loader->has_bgcolor = 0;
104,643✔
1557
    loader->bgcolor_source = SIXEL_LOADER_BGCOLOR_SOURCE_EXPLICIT;
104,643✔
1558
    loader->loop_control = SIXEL_LOOP_AUTO;
104,643✔
1559
    loader->finsecure = 0;
104,643✔
1560
    loader->has_start_frame_no = 0;
104,643✔
1561
    loader->start_frame_no = INT_MIN;
104,643✔
1562
    loader->cancel_flag = NULL;
104,643✔
1563
    loader->context = NULL;
104,643✔
1564
    /*
1565
     * Initialize a private logger. The helper reuses an existing global
1566
     * logger sink when present so loader markers share the timeline with
1567
     * upstream stages without requiring sixel_loader_setopt().
1568
     */
1569
    sixel_logger_init(&loader->logger);
104,643✔
1570
    (void)sixel_logger_prepare_env(&loader->logger);
104,643✔
1571
    loader->loader_order = NULL;
104,643✔
1572
    sixel_option_init_argument_list_resolution(&loader->loader_order_resolution);
104,643✔
1573
    loader->allocator = local_allocator;
104,643✔
1574
    loader->last_loader_name[0] = '\0';
104,643✔
1575
    loader->last_source_path[0] = '\0';
104,643✔
1576
    loader->last_input_bytes = 0u;
104,643✔
1577
    loader->callback_failed = 0;
104,643✔
1578
    loader->log_loader_finished = 0;
104,643✔
1579
    loader->log_path[0] = '\0';
104,643✔
1580
    loader->log_loader_name[0] = '\0';
104,643✔
1581
    loader->log_input_bytes = 0u;
104,643✔
1582
    loader->log_timeline_job_seq = 0;
104,643✔
1583
    loader->timeline_manager_select_job = -1;
104,643✔
1584
    loader->timeline_manager_select_open = 0;
104,643✔
1585
    loader->timeline_candidate_select_job = -1;
104,643✔
1586
    loader->timeline_candidate_select_open = 0;
104,643✔
1587
    loader->timeline_candidate_worker[0] = '\0';
104,643✔
1588

1589
    *pploader = loader;
104,643✔
1590
    status = SIXEL_OK;
104,643✔
1591

1592
end:
52,260✔
1593
    return status;
133,927✔
1594
}
29,284✔
1595

1596
SIXELAPI void
1597
sixel_loader_ref(
987,510✔
1598
    sixel_loader_t /* in */ *loader)
1599
{
1600
    if (loader == NULL) {
987,510✔
1601
        return;
1602
    }
1603

1604
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
987,510✔
1605
}
497,220✔
1606

1607
SIXELAPI void
1608
sixel_loader_unref(
1,092,141✔
1609
    sixel_loader_t /* in */ *loader)
1610
{
1611
    sixel_allocator_t *allocator;
579,566✔
1612
    unsigned int previous;
579,566✔
1613

1614
    if (loader == NULL) {
1,092,141✔
1615
        return;
1616
    }
1617

1618
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
1,092,141✔
1619
    if (previous == 1U) {
1,092,141✔
1620
        allocator = loader->allocator;
104,637✔
1621
        sixel_logger_close(&loader->logger);
104,637✔
1622
        sixel_allocator_free(allocator, loader->loader_order);
104,637✔
1623
        sixel_option_free_argument_list_resolution(
104,637✔
1624
            &loader->loader_order_resolution);
52,382✔
1625
        sixel_allocator_free(allocator, loader);
104,637✔
1626
        sixel_allocator_unref(allocator);
104,637✔
1627
    }
52,382✔
1628
}
549,601!
1629

1630
SIXELAPI SIXELSTATUS
1631
sixel_loader_setopt(
882,867✔
1632
    sixel_loader_t /* in */ *loader,
1633
    int            /* in */ option,
1634
    void const     /* in */ *value)
1635
{
1636
    SIXELSTATUS status = SIXEL_FALSE;
882,867✔
1637
    int const *flag;
468,640✔
1638
    unsigned char const *color;
468,640✔
1639
    char const *order;
468,640✔
1640
    char const *canonical_order;
468,640✔
1641
    char *copy;
468,640✔
1642
    sixel_allocator_t *allocator;
468,640✔
1643
    char match_detail[128];
468,640✔
1644
    sixel_option_argument_list_resolution_t parsed_order;
468,640✔
1645

1646
    flag = NULL;
882,867✔
1647
    color = NULL;
882,867✔
1648
    order = NULL;
882,867✔
1649
    canonical_order = NULL;
882,867✔
1650
    copy = NULL;
882,867✔
1651
    allocator = NULL;
882,867✔
1652
    match_detail[0] = '\0';
882,867✔
1653
    sixel_option_init_argument_list_resolution(&parsed_order);
882,867✔
1654

1655
    if (loader == NULL) {
882,867!
1656
        sixel_helper_set_additional_message(
×
1657
            "sixel_loader_setopt: loader is null.");
1658
        status = SIXEL_BAD_ARGUMENT;
×
1659
        goto end0;
×
1660
    }
1661

1662
    sixel_loader_ref(loader);
882,867✔
1663

1664
    switch (option) {
882,867!
1665
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
50,696!
1666
        flag = (int const *)value;
101,649✔
1667
        loader->fstatic = flag != NULL ? *flag : 0;
101,649!
1668
        status = SIXEL_OK;
101,649✔
1669
        break;
101,649✔
1670
    case SIXEL_LOADER_OPTION_USE_PALETTE:
50,696!
1671
        flag = (int const *)value;
101,649✔
1672
        loader->fuse_palette = flag != NULL ? *flag : 1;
101,649!
1673
        status = SIXEL_OK;
101,649✔
1674
        break;
101,649✔
1675
    case SIXEL_LOADER_OPTION_REQCOLORS:
50,696!
1676
        flag = (int const *)value;
101,649✔
1677
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
101,649!
1678
        if (loader->reqcolors < 1) {
101,649!
1679
            sixel_helper_set_additional_message(
×
1680
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1681
            status = SIXEL_BAD_ARGUMENT;
×
1682
            goto end;
×
1683
        }
1684
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
101,649!
1685
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1686
        }
1687
        status = SIXEL_OK;
76,249✔
1688
        break;
76,249✔
1689
    case SIXEL_LOADER_OPTION_BGCOLOR:
26,190!
1690
        if (value == NULL) {
53,477✔
1691
            loader->has_bgcolor = 0;
50,994✔
1692
        } else {
25,980✔
1693
            color = (unsigned char const *)value;
2,483✔
1694
            loader->bgcolor[0] = color[0];
2,483✔
1695
            loader->bgcolor[1] = color[1];
2,483✔
1696
            loader->bgcolor[2] = color[2];
2,483✔
1697
            loader->has_bgcolor = 1;
2,483✔
1698
            if (loader->bgcolor_source != SIXEL_LOADER_BGCOLOR_SOURCE_ENV) {
2,483!
1699
                loader->bgcolor_source = SIXEL_LOADER_BGCOLOR_SOURCE_EXPLICIT;
2,483✔
1700
            }
1,307✔
1701
        }
1702
        status = SIXEL_OK;
40,359✔
1703
        break;
40,359✔
1704
    case SIXEL_LOADER_OPTION_BGCOLOR_SOURCE:
26,134!
1705
        flag = (int const *)value;
53,377✔
1706
        if (flag == NULL) {
53,377!
1707
            loader->bgcolor_source = SIXEL_LOADER_BGCOLOR_SOURCE_EXPLICIT;
×
1708
            status = SIXEL_OK;
×
1709
            break;
×
1710
        }
1711
        if (*flag != SIXEL_LOADER_BGCOLOR_SOURCE_EXPLICIT &&
53,377!
1712
            *flag != SIXEL_LOADER_BGCOLOR_SOURCE_ENV) {
110!
1713
            sixel_helper_set_additional_message(
×
1714
                "sixel_loader_setopt: bgcolor source is invalid.");
1715
            status = SIXEL_BAD_ARGUMENT;
×
1716
            goto end;
×
1717
        }
1718
        loader->bgcolor_source = *flag;
53,377✔
1719
        status = SIXEL_OK;
53,377✔
1720
        break;
53,377✔
1721
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
50,696!
1722
        flag = (int const *)value;
101,649✔
1723
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
101,649!
1724
        status = SIXEL_OK;
101,649✔
1725
        break;
101,649✔
1726
    case SIXEL_LOADER_OPTION_INSECURE:
50,696!
1727
        flag = (int const *)value;
101,649✔
1728
        loader->finsecure = flag != NULL ? *flag : 0;
101,649!
1729
        status = SIXEL_OK;
101,649✔
1730
        break;
101,649✔
1731
    case SIXEL_LOADER_OPTION_START_FRAME_NO:
26,134!
1732
        if (value == NULL) {
53,377✔
1733
            loader->has_start_frame_no = 0;
50,959✔
1734
            loader->start_frame_no = INT_MIN;
50,959✔
1735
        } else {
25,907✔
1736
            flag = (int const *)value;
2,418✔
1737
            loader->start_frame_no = *flag;
2,418✔
1738
            loader->has_start_frame_no = 1;
2,418✔
1739
        }
1740
        status = SIXEL_OK;
40,287✔
1741
        break;
40,287✔
1742
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
26,134!
1743
        loader->cancel_flag = (int const *)value;
53,377✔
1744
        status = SIXEL_OK;
53,377✔
1745
        break;
53,377✔
1746
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
27,698!
1747
        allocator = loader->allocator;
56,371✔
1748
        if (value != NULL) {
56,371✔
1749
            order = (char const *)value;
45,127✔
1750
            status = sixel_loader_order_parse_and_validate(order,
68,809✔
1751
                                                           &parsed_order,
1752
                                                           match_detail,
23,682✔
1753
                                                           sizeof(match_detail));
1754
            if (SIXEL_FAILED(status)) {
45,127!
1755
                goto end;
×
1756
            }
1757
            canonical_order = parsed_order.canonical_argument;
45,127✔
1758
            if (canonical_order == NULL) {
45,127!
1759
                canonical_order = "";
×
1760
            }
1761
            copy = loader_strdup(canonical_order, allocator);
45,127✔
1762
            if (copy == NULL) {
45,127!
1763
                sixel_helper_set_additional_message(
×
1764
                    "sixel_loader_setopt: loader_strdup() failed.");
1765
                status = SIXEL_BAD_ALLOCATION;
×
1766
                goto end;
×
1767
            }
1768
        }
23,682✔
1769
        sixel_allocator_free(allocator, loader->loader_order);
56,371✔
1770
        loader->loader_order = copy;
56,371✔
1771
        copy = NULL;
56,371✔
1772
        if (value != NULL) {
56,371✔
1773
            sixel_option_move_argument_list_resolution(
45,127✔
1774
                &loader->loader_order_resolution,
23,682✔
1775
                &parsed_order);
1776
        } else {
23,682✔
1777
            sixel_option_free_argument_list_resolution(
11,244✔
1778
                &loader->loader_order_resolution);
4,991✔
1779
        }
1780
        status = SIXEL_OK;
42,499✔
1781
        break;
42,499✔
1782
    case SIXEL_LOADER_OPTION_CONTEXT:
52,260!
1783
        loader->context = (void *)value;
104,643✔
1784
        status = SIXEL_OK;
104,643✔
1785
        break;
104,643✔
1786
    default:
×
1787
        sixel_helper_set_additional_message(
×
1788
            "sixel_loader_setopt: unknown option.");
1789
        status = SIXEL_BAD_ARGUMENT;
×
1790
        goto end;
×
1791
    }
444,837✔
1792

1793
end:
218,588✔
1794
    if (copy != NULL) {
817,387!
1795
        sixel_allocator_free(loader->allocator, copy);
1796
    }
1797
    sixel_option_free_argument_list_resolution(&parsed_order);
882,867✔
1798
    sixel_loader_unref(loader);
882,867✔
1799

1800
end0:
438,030✔
1801
    return status;
1,132,065✔
1802
}
249,198✔
1803

1804
SIXELAPI char const *
1805
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
94,929✔
1806
{
1807
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
94,929!
1808
        return NULL;
18✔
1809
    }
1810
    return loader->last_loader_name;
94,904✔
1811
}
48,631✔
1812

1813
SIXELAPI char const *
1814
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
94,473✔
1815
{
1816
    if (loader == NULL || loader->last_source_path[0] == '\0') {
94,473!
1817
        return NULL;
357✔
1818
    }
1819
    return loader->last_source_path;
93,992✔
1820
}
48,408✔
1821

1822
SIXELAPI size_t
1823
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
47,477✔
1824
{
1825
    if (loader == NULL) {
47,477✔
1826
        return 0u;
1827
    }
1828
    return loader->last_input_bytes;
47,477✔
1829
}
24,321✔
1830

1831
int
1832
sixel_loader_get_start_frame_no(sixel_loader_t const *loader,
1833
                                int *start_frame_no)
1834
{
1835
    if (start_frame_no != NULL) {
×
1836
        *start_frame_no = INT_MIN;
×
1837
    }
1838
    if (loader == NULL || start_frame_no == NULL ||
×
1839
        loader->has_start_frame_no == 0) {
×
1840
        return 0;
1841
    }
1842

1843
    *start_frame_no = loader->start_frame_no;
×
1844
    return 1;
×
1845
}
1846

1847
static SIXELSTATUS
1848
loader_manager_configure_component(sixel_loader_component_t *component,
105,930✔
1849
                                   void *context)
1850
{
1851
    sixel_loader_component_option_context_t *options;
56,190✔
1852

1853
    options = (sixel_loader_component_option_context_t *)context;
105,930✔
1854
    if (options == NULL || options->loader == NULL) {
105,930!
1855
        sixel_helper_set_additional_message(
×
1856
            "loader_manager_configure_component: invalid context.");
1857
        return SIXEL_BAD_ARGUMENT;
×
1858
    }
1859

1860
    return loader_apply_component_options(component,
159,304✔
1861
                                          options->loader,
79,582✔
1862
                                          options->reqcolors,
53,374✔
1863
                                          &options->suboptions);
105,930✔
1864
}
53,374✔
1865

1866
static void
1867
loader_manager_trace_try_callback(char const *name, void *context)
105,930✔
1868
{
1869
    sixel_loader_manager_trace_context_t *trace;
56,190✔
1870
    char const *backend_name;
56,190✔
1871

1872
    trace = (sixel_loader_manager_trace_context_t *)context;
105,930✔
1873
    if (trace == NULL || trace->loader == NULL) {
105,930!
1874
        return;
1875
    }
1876
    backend_name = name != NULL ? name : "unknown";
105,930!
1877

1878
    trace->loader->log_input_bytes = trace->input_bytes;
105,930✔
1879
    if (name != NULL) {
105,930!
1880
        (void)sixel_compat_snprintf(trace->loader->log_loader_name,
159,304✔
1881
                                    sizeof(trace->loader->log_loader_name),
1882
                                    "%s",
1883
                                    name);
53,374✔
1884
    } else {
53,374✔
1885
        trace->loader->log_loader_name[0] = '\0';
×
1886
    }
1887
    (void)sixel_compat_snprintf(trace->loader->timeline_candidate_worker,
159,304✔
1888
                                sizeof(
1889
                                    trace->loader->timeline_candidate_worker),
1890
                                "loader/%s",
1891
                                backend_name);
53,374✔
1892
    loader_timeline_select_phase_start(
105,930✔
1893
        trace->loader,
53,374✔
1894
        trace->loader->timeline_candidate_worker,
105,930✔
1895
        &trace->loader->timeline_candidate_select_job,
79,582✔
1896
        &trace->loader->timeline_candidate_select_open);
105,930✔
1897
    trace->current_select_job = trace->loader->timeline_candidate_select_job;
105,930✔
1898
    (void)sixel_compat_snprintf(trace->current_worker,
159,304✔
1899
                                sizeof(trace->current_worker),
1900
                                "%s",
1901
                                trace->loader->timeline_candidate_worker);
105,930✔
1902
    loader_trace_try(name);
105,930✔
1903
}
53,374!
1904

1905
static void
1906
loader_manager_trace_result_callback(char const *name,
105,924✔
1907
                                     SIXELSTATUS status,
1908
                                     void *context)
1909
{
1910
    sixel_loader_manager_trace_context_t *trace;
56,187✔
1911
    char const *event;
56,187✔
1912

1913
    trace = (sixel_loader_manager_trace_context_t *)context;
105,924✔
1914
    event = NULL;
105,924✔
1915
    if (trace != NULL && trace->loader != NULL) {
105,924!
1916
        event = SIXEL_SUCCEEDED(status) ? "finish" : "fail";
105,924!
1917
        loader_timeline_select_phase_finish(
105,924✔
1918
            trace->loader,
53,373✔
1919
            trace->loader->timeline_candidate_worker,
105,924✔
1920
            &trace->loader->timeline_candidate_select_job,
79,579✔
1921
            &trace->loader->timeline_candidate_select_open,
79,579✔
1922
            event);
53,373✔
1923
        trace->loader->timeline_candidate_worker[0] = '\0';
105,924✔
1924
        trace->current_select_job = -1;
105,924✔
1925
        trace->current_worker[0] = '\0';
105,924✔
1926
    }
53,373✔
1927
    loader_trace_result(name, status);
105,924✔
1928
}
105,924✔
1929

1930
SIXELAPI SIXELSTATUS
1931
sixel_loader_load_file(
104,642✔
1932
    sixel_loader_t         /* in */ *loader,
1933
    char const             /* in */ *filename,
1934
    sixel_load_image_function /* in */ fn_load)
1935
{
1936
    SIXELSTATUS status = SIXEL_FALSE;
104,642✔
1937
    sixel_chunk_t *pchunk;
55,466✔
1938
    sixel_loader_entry_t const **plan;
55,466✔
1939
    sixel_loader_entry_t const *entries;
55,466✔
1940
    sixel_loader_factory_t *factory;
55,466✔
1941
    sixel_loader_manager_t *manager;
55,466✔
1942
    sixel_loader_chain_t *chain;
55,466✔
1943
    size_t entry_count;
55,466✔
1944
    size_t plan_length;
55,466✔
1945
    int reqcolors;
55,466✔
1946
    char const *order_override;
55,466✔
1947
    char const *env_order;
55,466✔
1948
    sixel_option_argument_list_resolution_t const *active_order_resolution;
55,466✔
1949
    char const *selected_name;
55,466✔
1950
    sixel_loader_callback_state_t callback_state;
55,466✔
1951
    sixel_loader_component_option_context_t option_context;
55,466✔
1952
    sixel_loader_manager_trace_context_t trace_context;
55,466✔
1953
    sixel_option_argument_list_resolution_t order_resolution;
55,466✔
1954
    sixel_loader_suboptions_t active_suboptions;
55,466✔
1955
    sixel_loader_osc11_bg_query_job_t osc11_query_job;
55,466✔
1956
    char const *osc11_timeout_env;
55,466✔
1957
    int osc11_timeout_ms;
55,466✔
1958
    int osc11_bgcolor_applied;
55,466✔
1959
    int skip_predicate_gate;
55,466✔
1960
    int thread_status;
55,466✔
1961
    int wait_result;
55,466✔
1962
    int chunk_job_id;
55,466✔
1963

1964
    pchunk = NULL;
104,642✔
1965
    plan = NULL;
104,642✔
1966
    entries = NULL;
104,642✔
1967
    factory = NULL;
104,642✔
1968
    manager = NULL;
104,642✔
1969
    chain = NULL;
104,642✔
1970
    entry_count = 0;
104,642✔
1971
    plan_length = 0;
104,642✔
1972
    reqcolors = 0;
104,642✔
1973
    order_override = NULL;
104,642✔
1974
    env_order = NULL;
104,642✔
1975
    active_order_resolution = NULL;
104,642✔
1976
    selected_name = NULL;
104,642✔
1977
    osc11_timeout_env = NULL;
104,642✔
1978
    osc11_timeout_ms = SIXEL_LOADER_OSC11_BG_QUERY_TIMEOUT_DEFAULT_MS;
104,642✔
1979
    osc11_bgcolor_applied = 0;
104,642✔
1980
    skip_predicate_gate = 0;
104,642✔
1981
    thread_status = SIXEL_FALSE;
104,642✔
1982
    wait_result = 0;
104,642✔
1983
    chunk_job_id = -1;
104,642✔
1984
    sixel_option_init_argument_list_resolution(&order_resolution);
104,642✔
1985
    loader_manager_init_loader_suboptions(&active_suboptions);
104,642✔
1986
    loader_osc11_bg_query_job_init(&osc11_query_job);
104,642✔
1987
    memset(&option_context, 0, sizeof(option_context));
104,642!
1988
    memset(&trace_context, 0, sizeof(trace_context));
104,642✔
1989
    trace_context.current_select_job = -1;
104,642✔
1990
    trace_context.current_worker[0] = '\0';
104,642✔
1991

1992
    if (loader == NULL) {
104,642!
1993
        sixel_helper_set_additional_message(
×
1994
            "sixel_loader_load_file: loader is null.");
1995
        status = SIXEL_BAD_ARGUMENT;
×
1996
        goto end0;
×
1997
    }
1998

1999
    sixel_loader_ref(loader);
104,642✔
2000

2001
    loader->log_loader_finished = 0;
104,642✔
2002
    loader->log_loader_name[0] = '\0';
104,642✔
2003
    loader->log_input_bytes = 0u;
104,642✔
2004
    loader->log_timeline_job_seq = 0;
104,642✔
2005
    loader->timeline_manager_select_job = -1;
104,642✔
2006
    loader->timeline_manager_select_open = 0;
104,642✔
2007
    loader->timeline_candidate_select_job = -1;
104,642✔
2008
    loader->timeline_candidate_select_open = 0;
104,642✔
2009
    loader->timeline_candidate_worker[0] = '\0';
104,642✔
2010
    loader->log_path[0] = '\0';
104,642✔
2011
    if (filename != NULL) {
104,642✔
2012
        (void)sixel_compat_snprintf(loader->log_path,
156,486✔
2013
                                    sizeof(loader->log_path),
2014
                                    "%s",
2015
                                    filename);
52,218✔
2016
    }
52,218✔
2017

2018
    memset(&callback_state, 0, sizeof(callback_state));
104,642✔
2019
    callback_state.loader = loader;
104,642✔
2020
    callback_state.fn = fn_load;
104,642✔
2021
    callback_state.context = loader->context;
104,642✔
2022
    loader->callback_failed = 0;
104,642✔
2023

2024
    status = loader_factory_get_default(&factory);
104,642✔
2025
    if (SIXEL_FAILED(status)) {
104,642!
2026
        goto end;
×
2027
    }
2028
    entry_count = loader_factory_get_entries(factory, &entries);
104,642✔
2029

2030
    reqcolors = loader->reqcolors;
104,642✔
2031
    if (reqcolors > SIXEL_PALETTE_MAX) {
104,642!
2032
        reqcolors = SIXEL_PALETTE_MAX;
2033
    }
2034

2035
    osc11_timeout_env = sixel_compat_getenv(
99,529✔
2036
        SIXEL_LOADER_OSC11_BG_QUERY_TIMEOUT_ENV);
2037
    osc11_timeout_ms = sixel_loader_parse_osc11_bg_query_timeout_ms(
99,529✔
2038
        osc11_timeout_env);
47,269✔
2039

2040
    /*
2041
     * Launch OSC11 probing before sixel_chunk_new() so the terminal roundtrip
2042
     * overlaps with input loading. If thread creation is unavailable, fall
2043
     * back to synchronous probing and keep failures non-fatal.
2044
     */
2045
    if (loader_can_query_osc11_bgcolor(loader) != 0) {
99,529!
2046
        osc11_query_job.timeout_ms = osc11_timeout_ms;
2,004✔
2047
        thread_status = sixel_thread_create(
2,004✔
2048
            &osc11_query_job.thread,
225✔
2049
            loader_osc11_bg_query_thread_main,
2050
            &osc11_query_job);
2051
        if (SIXEL_SUCCEEDED(thread_status)) {
2,004!
2052
            osc11_query_job.started = 1;
2,004✔
2053
        } else {
225✔
2054
            osc11_query_job.status = sixel_tty_query_osc11_bgcolor(
×
2055
                osc11_query_job.bgcolor,
2056
                osc11_query_job.timeout_ms);
2057
            if (SIXEL_SUCCEEDED(osc11_query_job.status)) {
×
2058
                loader->bgcolor[0] = osc11_query_job.bgcolor[0];
×
2059
                loader->bgcolor[1] = osc11_query_job.bgcolor[1];
×
2060
                loader->bgcolor[2] = osc11_query_job.bgcolor[2];
×
2061
                loader->has_bgcolor = 1;
×
2062
                osc11_bgcolor_applied = 1;
×
2063
            }
2064
        }
2065
    }
225✔
2066

2067
    chunk_job_id = loader_timeline_next_job(loader);
104,642!
2068
    loader_log_timeline_event(loader,
157,024✔
2069
                              "loader/manager",
2070
                              "chunk/create",
2071
                              "start",
2072
                              chunk_job_id);
52,382✔
2073
    status = sixel_chunk_new(&pchunk,
104,642✔
2074
                             filename,
52,382✔
2075
                             loader->finsecure,
52,382✔
2076
                             loader->cancel_flag,
52,382✔
2077
                             loader->allocator);
52,382✔
2078
    if (status != SIXEL_OK) {
104,642!
2079
        loader_log_timeline_event(loader,
124✔
2080
                                  "loader/manager",
2081
                                  "chunk/create",
2082
                                  "fail",
2083
                                  chunk_job_id);
44✔
2084
        goto end;
80✔
2085
    }
2086
    loader_log_timeline_event(loader,
156,900✔
2087
                              "loader/manager",
2088
                              "chunk/create",
2089
                              "finish",
2090
                              chunk_job_id);
52,338✔
2091

2092
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
104,562!
2093
        status = SIXEL_OK;
25✔
2094
        goto end;
25✔
2095
    }
2096

2097
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
104,520!
2098
        (void)sixel_compat_snprintf(loader->log_path,
156,051✔
2099
                                    sizeof(loader->log_path),
2100
                                    "%s",
2101
                                    pchunk->source_path);
77,976✔
2102
    }
52,044✔
2103

2104
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
104,539!
2105
        status = SIXEL_LOGIC_ERROR;
2✔
2106
        goto end;
2✔
2107
    }
2108

2109
    status = SIXEL_FALSE;
104,537✔
2110
    order_override = loader->loader_order;
104,537✔
2111
    if (order_override == NULL) {
104,537✔
2112
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
59,436✔
2113
        if (env_order != NULL && env_order[0] != '\0') {
59,436!
2114
            order_override = env_order;
36✔
2115
        }
22✔
2116
    }
28,657✔
2117
    if (order_override != NULL && order_override[0] != '\0') {
87,550!
2118
        if (order_override == loader->loader_order) {
45,153✔
2119
            active_order_resolution = &loader->loader_order_resolution;
45,102✔
2120
        } else {
23,671✔
2121
            status = loader_manager_parse_loader_order(order_override,
51✔
2122
                                                       &order_resolution);
2123
            if (SIXEL_FAILED(status)) {
51✔
2124
                goto end;
35✔
2125
            }
2126
            active_order_resolution = &order_resolution;
13✔
2127
        }
2128
    }
23,681✔
2129
    loader_manager_resolve_loader_suboptions(active_order_resolution,
101,626✔
2130
                                             &active_suboptions);
2131
    if (active_order_resolution != NULL &&
121,074✔
2132
        active_order_resolution->has_trailing_bang &&
45,118!
2133
        active_order_resolution->item_count == 1u) {
38,620!
2134
        /*
2135
         * A forced single-loader order ("-L name!") should reach the loader
2136
         * implementation whenever the coarse magic check matches, so loader-
2137
         * specific diagnostics are preserved for invalid inputs.
2138
         */
2139
        skip_predicate_gate = 1;
55,731✔
2140
    }
20,675✔
2141

2142
    plan = sixel_allocator_malloc(loader->allocator,
175,720✔
2143
                                  entry_count * sizeof(*plan));
90,022✔
2144
    if (plan == NULL) {
104,502!
2145
        status = SIXEL_BAD_ALLOCATION;
×
2146
        goto end;
×
2147
    }
2148

2149
    plan_length = loader_manager_build_plan_from_resolution(
104,502✔
2150
        active_order_resolution,
52,315✔
2151
        entries,
52,315✔
2152
        entry_count,
52,315✔
2153
        plan,
52,315✔
2154
        entry_count);
52,315✔
2155
    if (plan_length == 0u) {
104,502!
2156
        if (active_order_resolution != NULL &&
×
2157
            active_order_resolution->canonical_argument != NULL &&
×
2158
            active_order_resolution->canonical_argument[0] != '\0') {
×
2159
            sixel_helper_set_additional_message(
×
2160
                "sixel_loader_load_file: no supported loader in loader "
2161
                "order.");
2162
            status = SIXEL_BAD_ARGUMENT;
×
2163
        } else {
2164
            sixel_helper_set_additional_message(
×
2165
                "sixel_loader_load_file: no available loader backend.");
2166
            status = SIXEL_LOADER_FAILED;
×
2167
        }
2168
        goto end;
×
2169
    }
2170

2171
    status = loader_manager_get_default(&manager);
104,502✔
2172
    if (SIXEL_FAILED(status)) {
104,502!
2173
        goto end;
×
2174
    }
2175

2176
    status = loader_manager_build_chain_from_plan(manager,
156,817✔
2177
                                                  plan,
52,315✔
2178
                                                  plan_length,
52,315✔
2179
                                                  pchunk,
52,315✔
2180
                                                  skip_predicate_gate,
52,315✔
2181
                                                  loader->allocator,
52,315✔
2182
                                                  &chain);
2183
    if (SIXEL_FAILED(status)) {
104,502!
2184
        goto end;
×
2185
    }
2186

2187
    /*
2188
     * Before propagating component options, wait at most once for OSC11 query
2189
     * completion. Timeout keeps the previous behavior (no explicit bgcolor).
2190
     */
2191
    if (osc11_query_job.started != 0 && loader->has_bgcolor == 0) {
104,502!
2192
        wait_result = loader_osc11_bg_query_job_is_finished(&osc11_query_job);
1,986✔
2193
        if (wait_result == 0) {
1,986!
2194
            wait_result = sixel_loader_wait_for_condition(
1,303✔
2195
                loader_osc11_bg_query_job_is_finished,
2196
                &osc11_query_job,
2197
                osc11_timeout_ms);
208✔
2198
            (void)wait_result;
1,090✔
2199
        }
208✔
2200
        if (loader_osc11_bg_query_job_apply_if_ready(loader,
1,986!
2201
                                                     &osc11_query_job) != 0) {
223!
2202
            osc11_bgcolor_applied = 1;
2203
        }
2204
    }
223✔
2205

2206
    if (osc11_bgcolor_applied != 0) {
99,395!
2207
        /*
2208
         * OSC11 replies are terminal UI colors and therefore gamma-encoded.
2209
         * Force gamma interpretation for this load even when the process
2210
         * default requested a different background color space.
2211
         */
2212
        sixel_helper_set_loader_background_colorspace(SIXEL_COLORSPACE_GAMMA);
×
2213
    }
2214

2215
    option_context.loader = loader;
104,502✔
2216
    option_context.reqcolors = reqcolors;
104,502✔
2217
    option_context.suboptions = active_suboptions;
104,502✔
2218
    trace_context.loader = loader;
104,502✔
2219
    trace_context.input_bytes = pchunk->size;
104,502✔
2220
    loader_timeline_select_phase_start(loader,
156,817✔
2221
                                       "loader/manager",
2222
                                       &loader->timeline_manager_select_job,
52,315✔
2223
                                       &loader->timeline_manager_select_open);
52,315✔
2224
    status = loader_manager_execute_chain(
104,502✔
2225
        manager,
52,315✔
2226
        chain,
52,315✔
2227
        pchunk,
52,315✔
2228
        skip_predicate_gate,
52,315✔
2229
        loader_callback_trampoline,
2230
        &callback_state,
2231
        &loader->logger,
52,315✔
2232
        &loader->log_timeline_job_seq,
52,315✔
2233
        loader_manager_configure_component,
2234
        &option_context,
2235
        loader_manager_trace_try_callback,
2236
        loader_manager_trace_result_callback,
2237
        &trace_context,
2238
        &selected_name);
2239
    loader_timeline_select_phase_finish(
104,497✔
2240
        loader,
52,315✔
2241
        "loader/manager",
2242
        &loader->timeline_manager_select_job,
52,315✔
2243
        &loader->timeline_manager_select_open,
52,315✔
2244
        SIXEL_SUCCEEDED(status) ? "finish" : "fail");
104,497!
2245

2246
    if (SIXEL_FAILED(status)) {
104,497✔
2247
        if (status == SIXEL_FALSE) {
5,390!
2248
            if (!loader->callback_failed && pchunk != NULL) {
187!
2249
                status = SIXEL_LOADER_FAILED;
187✔
2250
                loader_publish_diagnostic(pchunk, filename);
187✔
2251
            } else {
187✔
2252
                sixel_helper_set_additional_message(
×
2253
                    "sixel_loader_load_file: loader returned "
2254
                    "unspecified failure.");
2255
                status = SIXEL_LOADER_FAILED;
×
2256
            }
2257
        }
187✔
2258
        goto end;
5,390✔
2259
    }
2260

2261
    if (selected_name != NULL) {
99,107!
2262
        (void)sixel_compat_snprintf(loader->last_loader_name,
148,743✔
2263
                                    sizeof(loader->last_loader_name),
2264
                                    "%s",
2265
                                    selected_name);
49,636✔
2266
    } else {
49,636✔
2267
        loader->last_loader_name[0] = '\0';
×
2268
    }
2269
    loader->last_input_bytes = pchunk->size;
99,107✔
2270
    if (pchunk->source_path != NULL) {
148,743✔
2271
        size_t path_len;
52,286✔
2272

2273
        path_len = strlen(pchunk->source_path);
98,651✔
2274
        if (path_len >= sizeof(loader->last_source_path)) {
98,651!
2275
            path_len = sizeof(loader->last_source_path) - 1u;
2276
        }
2277
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
98,651✔
2278
        loader->last_source_path[path_len] = '\0';
98,651✔
2279
    } else {
49,413✔
2280
        loader->last_source_path[0] = '\0';
456✔
2281
    }
2282

2283
end:
52,255✔
2284
    if (osc11_bgcolor_applied != 0) {
104,637!
2285
        sixel_helper_set_loader_background_colorspace(-1);
×
2286
    }
2287
    loader_osc11_bg_query_job_join(&osc11_query_job);
104,631✔
2288
    loader_chain_unref(chain);
104,631✔
2289
    chain = NULL;
104,631✔
2290
    loader_manager_unref(manager);
104,631✔
2291
    manager = NULL;
104,631✔
2292
    if (plan != NULL) {
104,631✔
2293
        sixel_allocator_free(loader->allocator, plan);
104,497✔
2294
        plan = NULL;
104,497✔
2295
    }
52,315✔
2296
    loader_factory_unref(factory);
104,637✔
2297
    factory = NULL;
104,637✔
2298
    sixel_chunk_destroy(pchunk);
104,637✔
2299
    sixel_option_free_argument_list_resolution(&order_resolution);
104,637✔
2300
    sixel_loader_unref(loader);
104,637✔
2301

2302
end0:
52,255✔
2303
    return status;
133,921✔
2304
}
29,284✔
2305

2306
/* load image from file */
2307

2308
SIXELAPI SIXELSTATUS
2309
sixel_helper_load_image_file(
2310
    char const                /* in */     *filename,     /* source file name */
2311
    int                       /* in */     fstatic,       /* whether to */
2312
                                                             /* extract a */
2313
                                                             /* static image */
2314
                                                             /* from an */
2315
                                                             /* animated gif */
2316
    int                       /* in */     fuse_palette,  /* whether to */
2317
                                                             /* use a */
2318
                                                             /* paletted */
2319
                                                             /* image; set */
2320
                                                             /* non-zero to */
2321
                                                             /* request one */
2322
    int                       /* in */     reqcolors,     /* requested */
2323
                                                             /* number of */
2324
                                                             /* colors; */
2325
                                                             /* should be */
2326
                                                             /* equal to or */
2327
                                                             /* less than */
2328
                                                             /* SIXEL_ */
2329
                                                             /* PALETTE_ */
2330
                                                             /* MAX */
2331
    unsigned char             /* in */     *bgcolor,      /* background */
2332
                                                             /* color, may */
2333
                                                             /* be NULL */
2334
    int                       /* in */     loop_control,  /* one of enum */
2335
                                                             /* loopControl */
2336
    sixel_load_image_function /* in */     fn_load,       /* callback */
2337
    int                       /* in */     finsecure,     /* true if do */
2338
                                                             /* not verify */
2339
                                                             /* SSL */
2340
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
2341
                                                             /* may be */
2342
                                                             /* NULL */
2343
    void                      /* in/out */ *context,      /* private data */
2344
                                                             /* passed to */
2345
                                                             /* callback */
2346
                                                             /* function, */
2347
                                                             /* may be */
2348
                                                             /* NULL */
2349
    sixel_allocator_t         /* in */     *allocator     /* allocator */
2350
                                                             /* object, */
2351
                                                             /* may be */
2352
                                                             /* NULL */
2353
)
2354
{
2355
    SIXELSTATUS status = SIXEL_FALSE;
×
2356
    sixel_loader_t *loader;
2357

2358
    loader = NULL;
×
2359

2360
    status = sixel_loader_new(&loader, allocator);
×
2361
    if (SIXEL_FAILED(status)) {
×
2362
        goto end;
×
2363
    }
2364

2365
    status = sixel_loader_setopt(loader,
×
2366
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2367
                                 &fstatic);
2368
    if (SIXEL_FAILED(status)) {
×
2369
        goto end;
×
2370
    }
2371

2372
    status = sixel_loader_setopt(loader,
×
2373
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2374
                                 &fuse_palette);
2375
    if (SIXEL_FAILED(status)) {
×
2376
        goto end;
×
2377
    }
2378

2379
    status = sixel_loader_setopt(loader,
×
2380
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2381
                                 &reqcolors);
2382
    if (SIXEL_FAILED(status)) {
×
2383
        goto end;
×
2384
    }
2385

2386
    status = sixel_loader_setopt(loader,
×
2387
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2388
                                 bgcolor);
2389
    if (SIXEL_FAILED(status)) {
×
2390
        goto end;
×
2391
    }
2392

2393
    status = sixel_loader_setopt(loader,
×
2394
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2395
                                 &loop_control);
2396
    if (SIXEL_FAILED(status)) {
×
2397
        goto end;
×
2398
    }
2399

2400
    status = sixel_loader_setopt(loader,
×
2401
                                 SIXEL_LOADER_OPTION_INSECURE,
2402
                                 &finsecure);
2403
    if (SIXEL_FAILED(status)) {
×
2404
        goto end;
×
2405
    }
2406

2407
    status = sixel_loader_setopt(loader,
×
2408
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2409
                                 cancel_flag);
2410
    if (SIXEL_FAILED(status)) {
×
2411
        goto end;
×
2412
    }
2413

2414
    status = sixel_loader_setopt(loader,
×
2415
                                 SIXEL_LOADER_OPTION_CONTEXT,
2416
                                 context);
2417
    if (SIXEL_FAILED(status)) {
×
2418
        goto end;
×
2419
    }
2420

2421
    status = sixel_loader_load_file(loader, filename, fn_load);
×
2422

2423
end:
2424
    sixel_loader_unref(loader);
×
2425

2426
    return status;
×
2427
}
2428

2429

2430
SIXELAPI size_t
2431
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
150✔
2432
{
2433
    sixel_loader_entry_t const *entries;
78✔
2434
    sixel_loader_factory_t *factory;
78✔
2435
    size_t entry_count;
78✔
2436
    size_t limit;
78✔
2437
    size_t index;
78✔
2438
    SIXELSTATUS status;
78✔
2439

2440
    entries = NULL;
150✔
2441
    factory = NULL;
150✔
2442
    entry_count = 0u;
150✔
2443
    limit = 0u;
150✔
2444
    index = 0u;
150✔
2445
    status = SIXEL_FALSE;
150✔
2446

2447
    status = loader_factory_get_default(&factory);
150✔
2448
    if (SIXEL_FAILED(status)) {
150!
2449
        return 0u;
2450
    }
2451
    entry_count = loader_factory_get_entries(factory, &entries);
150✔
2452

2453
    if (names != NULL && max_names > 0) {
150!
2454
        limit = entry_count;
75✔
2455
        if (limit > max_names) {
75!
2456
            limit = max_names;
2457
        }
2458
        for (index = 0; index < limit; ++index) {
369!
2459
            names[index] = entries[index].name;
294✔
2460
        }
207✔
2461
    }
33✔
2462

2463
    loader_factory_unref(factory);
150✔
2464

2465
    return entry_count;
150✔
2466
}
66✔
2467

2468

2469
/* emacs Local Variables:      */
2470
/* emacs mode: c               */
2471
/* emacs tab-width: 4          */
2472
/* emacs indent-tabs-mode: nil */
2473
/* emacs c-basic-offset: 4     */
2474
/* emacs End:                  */
2475
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2476
/* 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