Coverage Report

Created: 2026-06-30 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/hb-vector-paint-pdf.cc
Line
Count
Source
1
/*
2
 * Copyright © 2026  Behdad Esfahbod
3
 *
4
 *  This is part of HarfBuzz, a text shaping library.
5
 *
6
 * Permission is hereby granted, without written agreement and without
7
 * license or royalty fees, to use, copy, modify, and distribute this
8
 * software and its documentation for any purpose, provided that the
9
 * above copyright notice and the following two paragraphs appear in
10
 * all copies of this software.
11
 *
12
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16
 * DAMAGE.
17
 *
18
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23
 *
24
 * Author(s): Behdad Esfahbod
25
 */
26
27
#include "hb.hh"
28
29
#include "hb-vector-paint.hh"
30
#include "hb-vector-draw.hh"
31
#include "hb-paint.hh"
32
33
#include <math.h>
34
#include <stdio.h>
35
36
#ifdef HAVE_ZLIB
37
#include <zlib.h>
38
#endif
39
40
/* PDF paint backend for COLRv0/v1 color font rendering.
41
 *
42
 * Supports:
43
 *   - Solid colors with alpha (via ExtGState)
44
 *   - Glyph and rectangle clipping
45
 *   - Affine transforms
46
 *   - Groups (save/restore)
47
 *   - Linear gradients (PDF Type 2 axial shading)
48
 *   - Radial gradients (PDF Type 3 radial shading)
49
 *   - Sweep gradients (approximated via solid fallback)
50
 */
51
52
53
/* ---- PDF object collector ---- */
54
55
struct hb_pdf_obj_t
56
{
57
  hb_vector_buf_t data;
58
};
59
60
/* Collects extra PDF objects (shadings, functions, ExtGState)
61
 * during painting.  Referenced from the content stream by name
62
 * (e.g. /SH0, /GS0) and emitted at render time. */
63
static bool
64
hb_pdf_build_indexed_smask (hb_vector_buf_t *out,
65
          const char *idat_data, unsigned idat_len,
66
          unsigned width, unsigned height,
67
          const uint8_t *trns, unsigned trns_len);
68
69
struct hb_pdf_resources_t
70
{
71
  hb_vector_t<hb_pdf_obj_t> objects;   /* extra objects, starting at id 5 */
72
  hb_vector_buf_t extgstate_dict;    /* /GS0 5 0 R /GS1 6 0 R ... */
73
  hb_vector_buf_t shading_dict;      /* /SH0 7 0 R ... */
74
  hb_vector_buf_t xobject_dict;      /* /Im0 8 0 R ... */
75
  unsigned extgstate_count = 0;
76
  unsigned shading_count = 0;
77
  unsigned xobject_count = 0;
78
79
  unsigned add_object (hb_vector_buf_t &&obj_data)
80
863k
  {
81
863k
    unsigned id = 5 + objects.length; /* objects 1-4 are fixed */
82
863k
    hb_pdf_obj_t obj;
83
863k
    obj.data = std::move (obj_data);
84
863k
    objects.push (std::move (obj));
85
863k
    return id;
86
863k
  }
87
88
  /* Add ExtGState for fill opacity, return resource name index. */
89
  unsigned add_extgstate_alpha (float alpha)
90
18.9k
  {
91
18.9k
    unsigned idx = extgstate_count++;
92
18.9k
    hb_vector_buf_t obj;
93
18.9k
    obj.append_str ("<< /Type /ExtGState /ca ");
94
18.9k
    obj.append_num (alpha, 4);
95
18.9k
    obj.append_str (" >>");
96
18.9k
    unsigned obj_id = add_object (std::move (obj));
97
98
18.9k
    extgstate_dict.append_str ("/GS");
99
18.9k
    extgstate_dict.append_unsigned (idx);
100
18.9k
    extgstate_dict.append_c (' ');
101
18.9k
    extgstate_dict.append_unsigned (obj_id);
102
18.9k
    extgstate_dict.append_str (" 0 R ");
103
18.9k
    return idx;
104
18.9k
  }
105
106
  /* Add ExtGState for blend mode, return resource name index. */
107
  unsigned add_extgstate_blend (const char *bm)
108
1.95k
  {
109
1.95k
    unsigned idx = extgstate_count++;
110
1.95k
    hb_vector_buf_t obj;
111
1.95k
    obj.append_str ("<< /Type /ExtGState /BM /");
112
1.95k
    obj.append_str (bm);
113
1.95k
    obj.append_str (" >>");
114
1.95k
    unsigned obj_id = add_object (std::move (obj));
115
116
1.95k
    extgstate_dict.append_str ("/GS");
117
1.95k
    extgstate_dict.append_unsigned (idx);
118
1.95k
    extgstate_dict.append_c (' ');
119
1.95k
    extgstate_dict.append_unsigned (obj_id);
120
1.95k
    extgstate_dict.append_str (" 0 R ");
121
1.95k
    return idx;
122
1.95k
  }
123
124
  /* Add ExtGState with an SMask (soft mask) referencing a Form XObject
125
   * that paints a DeviceGray shading.  Returns the ExtGState resource index. */
126
  unsigned add_extgstate_smask (unsigned alpha_shading_id,
127
        float bbox_x, float bbox_y,
128
        float bbox_w, float bbox_h,
129
        unsigned precision)
130
22.6k
  {
131
    /* Form XObject: transparency group painting the alpha shading. */
132
22.6k
    hb_vector_buf_t form_stream;
133
22.6k
    form_stream.append_str ("/SHa sh\n");
134
135
22.6k
    hb_vector_buf_t form;
136
22.6k
    form.append_str ("<< /Type /XObject /Subtype /Form\n");
137
22.6k
    form.append_str ("/BBox [");
138
22.6k
    form.append_num (bbox_x, precision);
139
22.6k
    form.append_c (' ');
140
22.6k
    form.append_num (bbox_y, precision);
141
22.6k
    form.append_c (' ');
142
22.6k
    form.append_num (bbox_x + bbox_w, precision);
143
22.6k
    form.append_c (' ');
144
22.6k
    form.append_num (bbox_y + bbox_h, precision);
145
22.6k
    form.append_str ("]\n/Group << /S /Transparency /CS /DeviceGray >>\n");
146
22.6k
    form.append_str ("/Resources << /Shading << /SHa ");
147
22.6k
    form.append_unsigned (alpha_shading_id);
148
22.6k
    form.append_str (" 0 R >> >>\n");
149
22.6k
    form.append_str ("/Length ");
150
22.6k
    form.append_unsigned (form_stream.length);
151
22.6k
    form.append_str (" >>\nstream\n");
152
22.6k
    form.append_len (form_stream.arrayZ, form_stream.length);
153
22.6k
    form.append_str ("endstream");
154
22.6k
    unsigned form_id = add_object (std::move (form));
155
156
    /* ExtGState with luminosity soft mask. */
157
22.6k
    unsigned idx = extgstate_count++;
158
22.6k
    hb_vector_buf_t gs;
159
22.6k
    gs.append_str ("<< /Type /ExtGState\n");
160
22.6k
    gs.append_str ("/SMask << /Type /Mask /S /Luminosity /G ");
161
22.6k
    gs.append_unsigned (form_id);
162
22.6k
    gs.append_str (" 0 R >> >>");
163
22.6k
    unsigned gs_id = add_object (std::move (gs));
164
165
22.6k
    extgstate_dict.append_str ("/GS");
166
22.6k
    extgstate_dict.append_unsigned (idx);
167
22.6k
    extgstate_dict.append_c (' ');
168
22.6k
    extgstate_dict.append_unsigned (gs_id);
169
22.6k
    extgstate_dict.append_str (" 0 R ");
170
22.6k
    return idx;
171
22.6k
  }
172
173
  /* Add a shading, return resource name index. */
174
  unsigned add_shading (hb_vector_buf_t &&shading_data)
175
60.3k
  {
176
60.3k
    unsigned obj_id = add_object (std::move (shading_data));
177
60.3k
    return add_shading_by_id (obj_id);
178
60.3k
  }
179
180
  /* Register an already-allocated object as a shading resource. */
181
  unsigned add_shading_by_id (unsigned obj_id)
182
62.7k
  {
183
62.7k
    unsigned idx = shading_count++;
184
62.7k
    shading_dict.append_str ("/SH");
185
62.7k
    shading_dict.append_unsigned (idx);
186
62.7k
    shading_dict.append_c (' ');
187
62.7k
    shading_dict.append_unsigned (obj_id);
188
62.7k
    shading_dict.append_str (" 0 R ");
189
62.7k
    return idx;
190
62.7k
  }
191
192
  /* Add an XObject image, return resource name index.
193
   * idat_data/idat_len is the concatenated PNG IDAT payload (zlib data).
194
   * colors is 1 (gray), 3 (RGB), or 4 (RGBA); for indexed, pass colors=1.
195
   * plte/plte_len is the PLTE chunk data for indexed images (may be null). */
196
  unsigned add_xobject_png_image (const char *idat_data, unsigned idat_len,
197
          unsigned width, unsigned height,
198
          unsigned colors, bool has_alpha,
199
          const uint8_t *plte = nullptr,
200
          unsigned plte_len = 0,
201
          const uint8_t *trns = nullptr,
202
          unsigned trns_len = 0)
203
40
  {
204
40
    unsigned idx = xobject_count++;
205
40
    hb_vector_buf_t obj;
206
40
    obj.append_str ("<< /Type /XObject /Subtype /Image\n");
207
40
    obj.append_str ("/Width ");
208
40
    obj.append_unsigned (width);
209
40
    obj.append_str (" /Height ");
210
40
    obj.append_unsigned (height);
211
40
    obj.append_str ("\n/BitsPerComponent 8\n");
212
213
40
    if (plte && plte_len >= 3)
214
38
    {
215
      /* Indexed color: /ColorSpace [/Indexed /DeviceRGB N <hex palette>] */
216
38
      unsigned n_entries = plte_len / 3;
217
38
      obj.append_str ("/ColorSpace [/Indexed /DeviceRGB ");
218
38
      obj.append_unsigned (n_entries - 1);
219
38
      obj.append_str (" <");
220
9.86k
      for (unsigned i = 0; i < n_entries * 3; i++)
221
9.82k
      {
222
9.82k
  obj.append_c ("0123456789ABCDEF"[plte[i] >> 4]);
223
9.82k
  obj.append_c ("0123456789ABCDEF"[plte[i] & 0xF]);
224
9.82k
      }
225
38
      obj.append_str (">]\n");
226
38
    }
227
2
    else
228
2
    {
229
2
      obj.append_str ("/ColorSpace ");
230
2
      unsigned color_channels = has_alpha ? colors - 1 : colors;
231
2
      obj.append_str (color_channels == 1 ? "/DeviceGray" : "/DeviceRGB");
232
2
      obj.append_c ('\n');
233
2
    }
234
235
    /* Build SMask for indexed images with tRNS transparency. */
236
40
    unsigned smask_id = 0;
237
40
    if (plte && trns && trns_len)
238
38
    {
239
38
      hb_vector_buf_t smask_stream;
240
38
      if (hb_pdf_build_indexed_smask (&smask_stream, idat_data, idat_len,
241
38
              width, height, trns, trns_len))
242
25
      {
243
25
  hb_vector_buf_t smask_obj;
244
25
  smask_obj.append_str ("<< /Type /XObject /Subtype /Image\n");
245
25
  smask_obj.append_str ("/Width ");
246
25
  smask_obj.append_unsigned (width);
247
25
  smask_obj.append_str (" /Height ");
248
25
  smask_obj.append_unsigned (height);
249
25
  smask_obj.append_str ("\n/ColorSpace /DeviceGray /BitsPerComponent 8\n");
250
25
  smask_obj.append_str ("/Length ");
251
25
  smask_obj.append_unsigned (smask_stream.length);
252
25
  smask_obj.append_str (" >>\nstream\n");
253
25
  smask_obj.append_len (smask_stream.arrayZ, smask_stream.length);
254
25
  smask_obj.append_str ("\nendstream");
255
25
  smask_id = add_object (std::move (smask_obj));
256
25
      }
257
38
    }
258
259
40
    if (smask_id)
260
25
    {
261
25
      obj.append_str ("/SMask ");
262
25
      obj.append_unsigned (smask_id);
263
25
      obj.append_str (" 0 R\n");
264
25
    }
265
266
40
    obj.append_str ("/Filter /FlateDecode\n");
267
40
    obj.append_str ("/DecodeParms << /Predictor 15 /Colors ");
268
40
    obj.append_unsigned (colors);
269
40
    obj.append_str (" /BitsPerComponent 8 /Columns ");
270
40
    obj.append_unsigned (width);
271
40
    obj.append_str (" >>\n");
272
40
    obj.append_str ("/Length ");
273
40
    obj.append_unsigned (idat_len);
274
40
    obj.append_str (" >>\nstream\n");
275
40
    obj.append_len (idat_data, idat_len);
276
40
    obj.append_str ("\nendstream");
277
278
40
    (void) has_alpha;
279
280
40
    unsigned obj_id = add_object (std::move (obj));
281
282
40
    xobject_dict.append_str ("/Im");
283
40
    xobject_dict.append_unsigned (idx);
284
40
    xobject_dict.append_c (' ');
285
40
    xobject_dict.append_unsigned (obj_id);
286
40
    xobject_dict.append_str (" 0 R ");
287
40
    return idx;
288
40
  }
289
};
290
291
/* Store resources pointer in the paint struct's defs buffer
292
 * (repurposed — defs is unused for PDF). */
293
static hb_pdf_resources_t *
294
hb_pdf_get_resources (hb_vector_paint_t *paint)
295
102k
{
296
102k
  if (!paint->defs.length)
297
12.2k
  {
298
    /* First call: allocate and store. */
299
12.2k
    auto *res = (hb_pdf_resources_t *) hb_calloc (1, sizeof (hb_pdf_resources_t));
300
12.2k
    if (unlikely (!res)) return nullptr;
301
11.7k
    new (res) hb_pdf_resources_t ();
302
11.7k
    if (unlikely (!paint->defs.resize (sizeof (void *))))
303
4.85k
    {
304
4.85k
      res->~hb_pdf_resources_t ();
305
4.85k
      hb_free (res);
306
4.85k
      return nullptr;
307
4.85k
    }
308
6.92k
    memcpy (paint->defs.arrayZ, &res, sizeof (void *));
309
6.92k
  }
310
97.1k
  hb_pdf_resources_t *res;
311
97.1k
  memcpy (&res, paint->defs.arrayZ, sizeof (void *));
312
97.1k
  return res;
313
102k
}
314
315
void
316
hb_vector_paint_pdf_free_resources (hb_vector_paint_t *paint)
317
34.5k
{
318
34.5k
  if (paint->defs.length >= sizeof (void *))
319
6.92k
  {
320
6.92k
    hb_pdf_resources_t *res;
321
6.92k
    memcpy (&res, paint->defs.arrayZ, sizeof (void *));
322
6.92k
    if (res)
323
6.92k
    {
324
6.92k
      res->~hb_pdf_resources_t ();
325
6.92k
      hb_free (res);
326
6.92k
    }
327
6.92k
  }
328
34.5k
  paint->defs.clear ();
329
34.5k
}
330
331
332
/* ---- helpers ---- */
333
334
335
/* Emit a glyph outline as PDF path operators into buf. */
336
static void
337
hb_pdf_emit_glyph_path (hb_vector_paint_t *paint,
338
       hb_font_t *font,
339
       hb_codepoint_t glyph,
340
       hb_vector_buf_t *buf)
341
505k
{
342
505k
  hb_vector_path_sink_t sink = {&paint->path, paint->get_precision (),
343
505k
             paint->x_scale_factor, paint->y_scale_factor};
344
505k
  paint->path.clear ();
345
505k
  hb_font_draw_glyph (font, glyph,
346
505k
           hb_vector_pdf_path_draw_funcs_get (),
347
505k
           &sink);
348
505k
  buf->append_len (paint->path.arrayZ, paint->path.length);
349
505k
  paint->path.clear ();
350
505k
}
351
352
353
/* ---- paint callbacks ---- */
354
355
static void
356
hb_pdf_paint_push_transform (hb_paint_funcs_t *,
357
           void *paint_data,
358
           float xx, float yx,
359
           float xy, float yy,
360
           float dx, float dy,
361
           void *)
362
817k
{
363
817k
  auto *paint = (hb_vector_paint_t *) paint_data;
364
817k
  if (unlikely (!paint->ensure_initialized ()))
365
197k
    return;
366
367
619k
  auto &body = paint->current_body ();
368
619k
  unsigned sprec = body.scale_precision ();
369
619k
  body.append_str ("q\n");
370
619k
  body.append_num (xx, sprec);
371
619k
  body.append_c (' ');
372
619k
  body.append_num (yx, sprec);
373
619k
  body.append_c (' ');
374
619k
  body.append_num (xy, sprec);
375
619k
  body.append_c (' ');
376
619k
  body.append_num (yy, sprec);
377
619k
  body.append_c (' ');
378
619k
  body.append_num (paint->sx (dx));
379
619k
  body.append_c (' ');
380
619k
  body.append_num (paint->sy (dy));
381
619k
  body.append_str (" cm\n");
382
619k
}
383
384
static void
385
hb_pdf_paint_pop_transform (hb_paint_funcs_t *,
386
          void *paint_data,
387
          void *)
388
817k
{
389
817k
  auto *paint = (hb_vector_paint_t *) paint_data;
390
817k
  if (unlikely (!paint->ensure_initialized ()))
391
198k
    return;
392
619k
  paint->current_body ().append_str ("Q\n");
393
619k
}
394
395
static void
396
hb_pdf_paint_push_clip_glyph (hb_paint_funcs_t *,
397
            void *paint_data,
398
            hb_codepoint_t glyph,
399
            hb_font_t *font,
400
            void *)
401
602k
{
402
602k
  auto *paint = (hb_vector_paint_t *) paint_data;
403
602k
  if (unlikely (!paint->ensure_initialized ()))
404
97.7k
    return;
405
406
505k
  auto &body = paint->current_body ();
407
505k
  body.append_str ("q\n");
408
505k
  hb_pdf_emit_glyph_path (paint, font, glyph, &body);
409
505k
  body.append_str ("W n\n");
410
505k
}
411
412
static void
413
hb_pdf_paint_push_clip_rectangle (hb_paint_funcs_t *,
414
          void *paint_data,
415
          float xmin, float ymin,
416
          float xmax, float ymax,
417
          void *)
418
3.47k
{
419
3.47k
  auto *paint = (hb_vector_paint_t *) paint_data;
420
3.47k
  if (unlikely (!paint->ensure_initialized ()))
421
571
    return;
422
423
2.90k
  auto &body = paint->current_body ();
424
2.90k
  body.append_str ("q\n");
425
2.90k
  body.append_num (paint->sx (xmin));
426
2.90k
  body.append_c (' ');
427
2.90k
  body.append_num (paint->sy (ymin));
428
2.90k
  body.append_c (' ');
429
2.90k
  body.append_num (paint->sx (xmax - xmin));
430
2.90k
  body.append_c (' ');
431
2.90k
  body.append_num (paint->sy (ymax - ymin));
432
2.90k
  body.append_str (" re W n\n");
433
2.90k
}
434
435
static hb_draw_funcs_t *
436
hb_pdf_paint_push_clip_path_start (hb_paint_funcs_t *,
437
           void *paint_data,
438
           void **draw_data,
439
           void *)
440
0
{
441
0
  auto *paint = (hb_vector_paint_t *) paint_data;
442
0
  if (unlikely (!paint->ensure_initialized ()))
443
0
  {
444
0
    *draw_data = nullptr;
445
0
    return nullptr;
446
0
  }
447
448
0
  auto &body = paint->current_body ();
449
0
  body.append_str ("q\n");
450
  /* Stream path operators straight into the body; end() seals
451
   * the path with "W n" to turn it into the clip region.
452
   * Coordinates arrive in font-scale; the sink divides by
453
   * scale_factor so they land in output space. */
454
0
  paint->clip_path_sink = {&body, paint->get_precision (),
455
0
         paint->x_scale_factor,
456
0
         paint->y_scale_factor};
457
0
  *draw_data = &paint->clip_path_sink;
458
0
  return hb_vector_pdf_path_draw_funcs_get ();
459
0
}
460
461
static void
462
hb_pdf_paint_push_clip_path_end (hb_paint_funcs_t *,
463
         void *paint_data,
464
         void *)
465
0
{
466
0
  auto *paint = (hb_vector_paint_t *) paint_data;
467
0
  if (unlikely (!paint->ensure_initialized ()))
468
0
    return;
469
0
  paint->current_body ().append_str ("W n\n");
470
0
}
471
472
static void
473
hb_pdf_paint_pop_clip (hb_paint_funcs_t *,
474
           void *paint_data,
475
           void *)
476
606k
{
477
606k
  auto *paint = (hb_vector_paint_t *) paint_data;
478
606k
  if (unlikely (!paint->ensure_initialized ()))
479
98.5k
    return;
480
507k
  paint->current_body ().append_str ("Q\n");
481
507k
}
482
483
/* Paint a solid color, including alpha via ExtGState. */
484
static void
485
hb_pdf_paint_solid_color (hb_vector_paint_t *paint, hb_color_t c)
486
424k
{
487
424k
  auto &body = paint->current_body ();
488
489
424k
  float r = hb_color_get_red (c) / 255.f;
490
424k
  float g = hb_color_get_green (c) / 255.f;
491
424k
  float b = hb_color_get_blue (c) / 255.f;
492
424k
  float a = hb_color_get_alpha (c) / 255.f;
493
494
424k
  if (a < 1.f / 255.f)
495
180k
    return;
496
497
  /* Set alpha via ExtGState if needed. */
498
244k
  if (a < 1.f - 1.f / 512.f)
499
19.7k
  {
500
19.7k
    auto *res = hb_pdf_get_resources (paint);
501
19.7k
    if (res)
502
18.9k
    {
503
18.9k
      unsigned gs_idx = res->add_extgstate_alpha (a);
504
18.9k
      body.append_str ("/GS");
505
18.9k
      body.append_unsigned (gs_idx);
506
18.9k
      body.append_str (" gs\n");
507
18.9k
    }
508
19.7k
  }
509
510
  /* Set fill color. */
511
244k
  body.append_num (r, 4);
512
244k
  body.append_c (' ');
513
244k
  body.append_num (g, 4);
514
244k
  body.append_c (' ');
515
244k
  body.append_num (b, 4);
516
244k
  body.append_str (" rg\n");
517
518
  /* Paint a huge rect (will be clipped). */
519
244k
  body.append_str ("-32767 -32767 65534 65534 re f\n");
520
244k
}
521
522
static void
523
hb_pdf_paint_color (hb_paint_funcs_t *,
524
        void *paint_data,
525
        hb_bool_t,
526
        hb_color_t color,
527
        void *)
528
491k
{
529
491k
  auto *paint = (hb_vector_paint_t *) paint_data;
530
491k
  if (unlikely (!paint->ensure_initialized ()))
531
66.8k
    return;
532
533
424k
  hb_pdf_paint_solid_color (paint, color);
534
424k
}
535
536
/* Build an uncompressed alpha mask from indexed PNG IDAT data + tRNS.
537
 * Decompresses IDAT, un-filters PNG rows, maps palette indices to alpha. */
538
static bool
539
hb_pdf_build_indexed_smask (hb_vector_buf_t *out,
540
          const char *idat_data, unsigned idat_len,
541
          unsigned width, unsigned height,
542
          const uint8_t *trns, unsigned trns_len)
543
38
{
544
#ifndef HAVE_ZLIB
545
  (void) out; (void) idat_data; (void) idat_len;
546
  (void) width; (void) height; (void) trns; (void) trns_len;
547
  return false;
548
#else
549
  /* Decompress IDAT (zlib). */
550
38
  unsigned raw_len = (width + 1) * height; /* 1 filter byte per row + width bytes */
551
38
  uint8_t *raw = (uint8_t *) hb_malloc (raw_len);
552
38
  if (!raw) return false;
553
38
  HB_SCOPE_GUARD (hb_free (raw));
554
555
38
  z_stream stream = {};
556
38
  stream.next_in = (Bytef *) idat_data;
557
38
  stream.avail_in = idat_len;
558
38
  stream.next_out = (Bytef *) raw;
559
38
  stream.avail_out = raw_len;
560
561
38
  if (inflateInit (&stream) != Z_OK)
562
0
    return false;
563
38
  int status = inflate (&stream, Z_FINISH);
564
38
  unsigned long total_out = stream.total_out;
565
38
  inflateEnd (&stream);
566
38
  if (status != Z_STREAM_END || total_out != raw_len)
567
13
    return false;
568
569
  /* Un-filter and map to alpha. */
570
25
  if (!out->resize (width * height))
571
0
    return false;
572
573
25
  uint8_t *unfiltered = (uint8_t *) hb_malloc (width);
574
25
  if (!unfiltered)
575
0
    return false;
576
25
  HB_SCOPE_GUARD (hb_free (unfiltered));
577
578
25
  uint8_t *prev_unfiltered = (uint8_t *) hb_calloc (width, 1);
579
25
  if (!prev_unfiltered)
580
0
    return false;
581
25
  HB_SCOPE_GUARD (hb_free (prev_unfiltered));
582
583
3.22k
  for (unsigned y = 0; y < height; y++)
584
3.20k
  {
585
3.20k
    uint8_t *row = raw + y * (width + 1);
586
3.20k
    uint8_t filter = row[0];
587
3.20k
    uint8_t *pixels = row + 1;
588
589
438k
    for (unsigned x = 0; x < width; x++)
590
435k
    {
591
435k
      uint8_t a = (x > 0) ? unfiltered[x - 1] : 0;
592
435k
      uint8_t b = prev_unfiltered[x];
593
435k
      uint8_t c = (x > 0) ? prev_unfiltered[x - 1] : 0;
594
435k
      uint8_t val = pixels[x];
595
596
435k
      switch (filter)
597
435k
      {
598
402k
      case 0: unfiltered[x] = val; break; /* None */
599
2.44k
      case 1: unfiltered[x] = val + a; break; /* Sub */
600
22.0k
      case 2: unfiltered[x] = val + b; break; /* Up */
601
0
      case 3: unfiltered[x] = val + (uint8_t) (((unsigned) a + b) / 2); break; /* Average */
602
8.16k
      case 4: /* Paeth */
603
8.16k
      {
604
8.16k
  int p = (int) a + (int) b - (int) c;
605
8.16k
  int pa = abs (p - (int) a);
606
8.16k
  int pb = abs (p - (int) b);
607
8.16k
  int pc = abs (p - (int) c);
608
8.16k
  uint8_t pr = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
609
8.16k
  unfiltered[x] = val + pr;
610
8.16k
  break;
611
0
      }
612
0
      default: unfiltered[x] = val; break;
613
435k
      }
614
615
      /* Map palette index to alpha. */
616
435k
      uint8_t idx = unfiltered[x];
617
435k
      out->arrayZ[y * width + x] = (idx < trns_len) ? trns[idx] : 0xFF;
618
435k
    }
619
620
    /* Swap buffers. */
621
3.20k
    uint8_t *tmp = prev_unfiltered;
622
3.20k
    prev_unfiltered = unfiltered;
623
3.20k
    unfiltered = tmp;
624
3.20k
  }
625
626
25
  return true;
627
25
#endif
628
25
}
629
630
/* Read a big-endian uint32 from a byte pointer. */
631
static inline uint32_t
632
hb_pdf_png_u32 (const uint8_t *p)
633
560
{
634
560
  return ((uint32_t) p[0] << 24) | ((uint32_t) p[1] << 16) |
635
560
   ((uint32_t) p[2] << 8)  | (uint32_t) p[3];
636
560
}
637
638
static hb_bool_t
639
hb_pdf_paint_image (hb_paint_funcs_t *,
640
        void *paint_data,
641
        hb_blob_t *image,
642
        unsigned width,
643
        unsigned height,
644
        hb_tag_t format,
645
        float slant HB_UNUSED,
646
        hb_glyph_extents_t *extents,
647
        void *)
648
7.54k
{
649
7.54k
  if (format != HB_TAG ('p','n','g',' '))
650
7.45k
    return false;
651
88
  if (!extents || !width || !height)
652
10
    return false;
653
654
78
  auto *paint = (hb_vector_paint_t *) paint_data;
655
78
  if (unlikely (!paint->ensure_initialized ()))
656
1
    return false;
657
77
  auto *res = hb_pdf_get_resources (paint);
658
77
  if (unlikely (!res))
659
1
    return false;
660
661
76
  unsigned len = 0;
662
76
  const uint8_t *data = (const uint8_t *) hb_blob_get_data (image, &len);
663
76
  if (!data || len < 8)
664
6
    return false;
665
666
  /* Verify PNG signature. */
667
70
  static const uint8_t png_sig[8] = {137, 80, 78, 71, 13, 10, 26, 10};
668
70
  if (hb_memcmp (data, png_sig, 8) != 0)
669
19
    return false;
670
671
  /* Parse PNG chunks: extract IHDR and concatenate IDAT payloads. */
672
51
  unsigned png_w = 0, png_h = 0;
673
51
  unsigned colors = 3; /* default RGB */
674
51
  uint8_t color_type = 2;
675
51
  bool has_alpha = false;
676
51
  const uint8_t *plte_data = nullptr;
677
51
  unsigned plte_len = 0;
678
51
  const uint8_t *trns_data = nullptr;
679
51
  unsigned trns_len = 0;
680
51
  hb_vector_buf_t idat;
681
682
51
  unsigned pos = 8;
683
229
  while (pos + 12 <= len)
684
229
  {
685
229
    uint32_t chunk_len = hb_pdf_png_u32 (data + pos);
686
229
    uint32_t chunk_type = hb_pdf_png_u32 (data + pos + 4);
687
229
    if (pos + 12 + chunk_len > len)
688
20
      break;
689
209
    const uint8_t *chunk_data = data + pos + 8;
690
691
209
    if (chunk_type == 0x49484452u) /* IHDR */
692
51
    {
693
51
      if (chunk_len < 13) return false;
694
51
      png_w = hb_pdf_png_u32 (chunk_data);
695
51
      png_h = hb_pdf_png_u32 (chunk_data + 4);
696
51
      uint8_t bit_depth = chunk_data[8];
697
51
      color_type = chunk_data[9];
698
51
      if (bit_depth != 8) return false; /* only 8-bit supported */
699
700
46
      switch (color_type)
701
46
      {
702
0
      case 0: colors = 1; has_alpha = false; break; /* Grayscale */
703
0
      case 2: colors = 3; has_alpha = false; break; /* RGB */
704
44
      case 3: colors = 1; has_alpha = false; break; /* Indexed */
705
0
      case 4: colors = 2; has_alpha = true;  break; /* Gray+Alpha */
706
2
      case 6: colors = 4; has_alpha = true;  break; /* RGBA */
707
0
      default: return false;
708
46
      }
709
46
    }
710
158
    else if (chunk_type == 0x504C5445u) /* PLTE */
711
38
    {
712
38
      plte_data = chunk_data;
713
38
      plte_len = chunk_len;
714
38
    }
715
120
    else if (chunk_type == 0x74524E53u) /* tRNS */
716
38
    {
717
38
      trns_data = chunk_data;
718
38
      trns_len = chunk_len;
719
38
    }
720
82
    else if (chunk_type == 0x49444154u) /* IDAT */
721
40
    {
722
40
      idat.append_len ((const char *) chunk_data, chunk_len);
723
40
    }
724
42
    else if (chunk_type == 0x49454E44u) /* IEND */
725
26
      break;
726
727
178
    pos += 12 + chunk_len;
728
178
  }
729
730
46
  if (!png_w || !png_h || !idat.length)
731
6
    return false;
732
733
40
  unsigned im_idx = res->add_xobject_png_image (idat.arrayZ, idat.length,
734
40
             png_w, png_h,
735
40
             colors, has_alpha,
736
40
             plte_data, plte_len,
737
40
             trns_data, trns_len);
738
739
  /* Emit: save state, set CTM to map image (0,0)-(1,1) to extents, paint. */
740
40
  auto &body = paint->current_body ();
741
40
  body.append_str ("q\n");
742
743
  /* Image space: (0,0) at bottom-left, (1,1) at top-right.
744
   * We need to map to extents (x_bearing, y_bearing+height) .. (x_bearing+width, y_bearing).
745
   * Since font coords are Y-up like PDF, y_bearing is the top. */
746
40
  float ix = (float) extents->x_bearing;
747
40
  float iy = (float) extents->y_bearing + (float) extents->height;
748
40
  float iw = (float) extents->width;
749
40
  float ih = (float) -extents->height; /* negative because image Y goes up but height is negative in extents */
750
751
40
  body.append_num (paint->sx (iw));
752
40
  body.append_str (" 0 0 ");
753
40
  body.append_num (paint->sy (ih));
754
40
  body.append_c (' ');
755
40
  body.append_num (paint->sx (ix));
756
40
  body.append_c (' ');
757
40
  body.append_num (paint->sy (iy));
758
40
  body.append_str (" cm\n");
759
760
40
  body.append_str ("/Im");
761
40
  body.append_unsigned (im_idx);
762
40
  body.append_str (" Do\nQ\n");
763
764
40
  return true;
765
46
}
766
767
/* ---- Gradient helpers ---- */
768
769
static bool
770
hb_pdf_gradient_needs_alpha (hb_array_t<const hb_color_stop_t> stops)
771
63.0k
{
772
63.0k
  for (const auto &s : stops)
773
135k
    if (hb_color_get_alpha (s.color) != 255)
774
22.9k
      return true;
775
40.1k
  return false;
776
63.0k
}
777
778
/* Build a PDF Type 2 (exponential interpolation) function for
779
 * a single alpha stop pair (DeviceGray, scalar output). */
780
static void
781
hb_pdf_build_alpha_interpolation_function (hb_vector_buf_t *obj,
782
             float a0, float a1)
783
307k
{
784
307k
  obj->append_str ("<< /FunctionType 2 /Domain [0 1] /N 1\n");
785
307k
  obj->append_str ("/C0 [");
786
307k
  obj->append_num (a0, 4);
787
307k
  obj->append_str ("]\n/C1 [");
788
307k
  obj->append_num (a1, 4);
789
307k
  obj->append_str ("] >>");
790
307k
}
791
792
/* Build a stitching function (Type 3) for the alpha channel of
793
 * pre-populated paint->color_stops_scratch (already sorted+normalized). */
794
static unsigned
795
hb_pdf_build_alpha_gradient_function_from_stops (hb_pdf_resources_t *res,
796
             hb_vector_paint_t *paint)
797
20.9k
{
798
20.9k
  unsigned count = paint->color_stops_scratch.length;
799
800
20.9k
  if (count < 2)
801
131
  {
802
131
    float a = count ? hb_color_get_alpha (paint->color_stops_scratch.arrayZ[0].color) / 255.f : 1.f;
803
131
    hb_vector_buf_t obj;
804
131
    hb_pdf_build_alpha_interpolation_function (&obj, a, a);
805
131
    return res->add_object (std::move (obj));
806
131
  }
807
808
20.8k
  if (count == 2)
809
10.3k
  {
810
10.3k
    hb_vector_buf_t obj;
811
10.3k
    hb_pdf_build_alpha_interpolation_function (&obj,
812
10.3k
      hb_color_get_alpha (paint->color_stops_scratch.arrayZ[0].color) / 255.f,
813
10.3k
      hb_color_get_alpha (paint->color_stops_scratch.arrayZ[1].color) / 255.f);
814
10.3k
    return res->add_object (std::move (obj));
815
10.3k
  }
816
817
10.4k
  hb_vector_t<unsigned> sub_func_ids;
818
307k
  for (unsigned i = 0; i + 1 < count; i++)
819
297k
  {
820
297k
    hb_vector_buf_t sub;
821
297k
    hb_pdf_build_alpha_interpolation_function (&sub,
822
297k
      hb_color_get_alpha (paint->color_stops_scratch.arrayZ[i].color) / 255.f,
823
297k
      hb_color_get_alpha (paint->color_stops_scratch.arrayZ[i + 1].color) / 255.f);
824
297k
    sub_func_ids.push (res->add_object (std::move (sub)));
825
297k
  }
826
827
10.4k
  hb_vector_buf_t obj;
828
10.4k
  obj.append_str ("<< /FunctionType 3 /Domain [0 1]\n");
829
10.4k
  obj.append_str ("/Functions [");
830
259k
  for (unsigned i = 0; i < sub_func_ids.length; i++)
831
248k
  {
832
248k
    if (i) obj.append_c (' ');
833
248k
    obj.append_unsigned (sub_func_ids.arrayZ[i]);
834
248k
    obj.append_str (" 0 R");
835
248k
  }
836
10.4k
  obj.append_str ("]\n/Bounds [");
837
297k
  for (unsigned i = 1; i + 1 < count; i++)
838
286k
  {
839
286k
    if (i > 1) obj.append_c (' ');
840
286k
    obj.append_num (paint->color_stops_scratch.arrayZ[i].offset, 4);
841
286k
  }
842
10.4k
  obj.append_str ("]\n/Encode [");
843
307k
  for (unsigned i = 0; i + 1 < count; i++)
844
297k
  {
845
297k
    if (i) obj.append_c (' ');
846
297k
    obj.append_str ("0 1");
847
297k
  }
848
10.4k
  obj.append_str ("] >>");
849
10.4k
  return res->add_object (std::move (obj));
850
20.8k
}
851
852
/* Build a PDF Type 2 (exponential interpolation) function for
853
 * a single color stop pair. */
854
static void
855
hb_pdf_build_interpolation_function (hb_vector_buf_t *obj,
856
             hb_color_t c0, hb_color_t c1)
857
372k
{
858
372k
  obj->append_str ("<< /FunctionType 2 /Domain [0 1] /N 1\n");
859
372k
  obj->append_str ("/C0 [");
860
372k
  obj->append_num (hb_color_get_red (c0) / 255.f, 4);
861
372k
  obj->append_c (' ');
862
372k
  obj->append_num (hb_color_get_green (c0) / 255.f, 4);
863
372k
  obj->append_c (' ');
864
372k
  obj->append_num (hb_color_get_blue (c0) / 255.f, 4);
865
372k
  obj->append_str ("]\n/C1 [");
866
372k
  obj->append_num (hb_color_get_red (c1) / 255.f, 4);
867
372k
  obj->append_c (' ');
868
372k
  obj->append_num (hb_color_get_green (c1) / 255.f, 4);
869
372k
  obj->append_c (' ');
870
372k
  obj->append_num (hb_color_get_blue (c1) / 255.f, 4);
871
372k
  obj->append_str ("] >>");
872
372k
}
873
874
/* Build a stitching function (Type 3) from pre-populated
875
 * paint->color_stops_scratch (already sorted+normalized). */
876
static unsigned
877
hb_pdf_build_gradient_function_from_stops (hb_pdf_resources_t *res,
878
             hb_vector_paint_t *paint)
879
60.3k
{
880
60.3k
  unsigned count = paint->color_stops_scratch.length;
881
882
60.3k
  if (count < 2)
883
139
  {
884
    /* Single stop: constant function. */
885
139
    hb_color_t c = count ? paint->color_stops_scratch.arrayZ[0].color
886
139
       : HB_COLOR (0, 0, 0, 255);
887
139
    hb_vector_buf_t obj;
888
139
    hb_pdf_build_interpolation_function (&obj, c, c);
889
139
    return res->add_object (std::move (obj));
890
139
  }
891
892
  /* Sort by offset. */
893
60.2k
  paint->color_stops_scratch.as_array ().qsort (
894
60.2k
    [] (const hb_color_stop_t &a, const hb_color_stop_t &b)
895
1.59M
    { return (a.offset > b.offset) - (a.offset < b.offset); });
896
897
60.2k
  if (count == 2)
898
39.0k
  {
899
    /* Two stops: single interpolation function. */
900
39.0k
    hb_vector_buf_t obj;
901
39.0k
    hb_pdf_build_interpolation_function (&obj,
902
39.0k
            paint->color_stops_scratch.arrayZ[0].color,
903
39.0k
            paint->color_stops_scratch.arrayZ[1].color);
904
39.0k
    return res->add_object (std::move (obj));
905
39.0k
  }
906
907
  /* Multiple stops: create sub-functions and stitch. */
908
21.1k
  hb_vector_t<unsigned> sub_func_ids;
909
354k
  for (unsigned i = 0; i + 1 < count; i++)
910
333k
  {
911
333k
    hb_vector_buf_t sub;
912
333k
    hb_pdf_build_interpolation_function (&sub,
913
333k
            paint->color_stops_scratch.arrayZ[i].color,
914
333k
            paint->color_stops_scratch.arrayZ[i + 1].color);
915
333k
    sub_func_ids.push (res->add_object (std::move (sub)));
916
333k
  }
917
918
  /* Stitching function (Type 3). */
919
21.1k
  hb_vector_buf_t obj;
920
21.1k
  obj.append_str ("<< /FunctionType 3 /Domain [0 1]\n");
921
922
  /* Functions array. */
923
21.1k
  obj.append_str ("/Functions [");
924
328k
  for (unsigned i = 0; i < sub_func_ids.length; i++)
925
307k
  {
926
307k
    if (i) obj.append_c (' ');
927
307k
    obj.append_unsigned (sub_func_ids.arrayZ[i]);
928
307k
    obj.append_str (" 0 R");
929
307k
  }
930
21.1k
  obj.append_str ("]\n");
931
932
  /* Bounds. */
933
21.1k
  obj.append_str ("/Bounds [");
934
333k
  for (unsigned i = 1; i + 1 < count; i++)
935
312k
  {
936
312k
    if (i > 1) obj.append_c (' ');
937
312k
    obj.append_num (paint->color_stops_scratch.arrayZ[i].offset, 4);
938
312k
  }
939
21.1k
  obj.append_str ("]\n");
940
941
  /* Encode array. */
942
21.1k
  obj.append_str ("/Encode [");
943
354k
  for (unsigned i = 0; i + 1 < count; i++)
944
333k
  {
945
333k
    if (i) obj.append_c (' ');
946
333k
    obj.append_str ("0 1");
947
333k
  }
948
21.1k
  obj.append_str ("] >>");
949
950
21.1k
  return res->add_object (std::move (obj));
951
60.2k
}
952
953
static void
954
hb_pdf_paint_linear_gradient (hb_paint_funcs_t *,
955
            void *paint_data,
956
            hb_color_line_t *color_line,
957
            float x0, float y0,
958
            float x1, float y1,
959
            float x2 HB_UNUSED, float y2 HB_UNUSED,
960
            void *)
961
50.1k
{
962
50.1k
  auto *paint = (hb_vector_paint_t *) paint_data;
963
50.1k
  if (unlikely (!paint->ensure_initialized ()))
964
14.4k
    return;
965
35.7k
  auto *res = hb_pdf_get_resources (paint);
966
35.7k
  if (unlikely (!res))
967
1.79k
    return;
968
969
  /* Fetch, normalize stops to [0,1], and adjust coordinates. */
970
33.9k
  if (!paint->fetch_color_stops (color_line))
971
1.41k
    return;
972
32.5k
  hb_vector_t<hb_color_stop_t> &stops = paint->color_stops_scratch;
973
974
32.5k
  float mn, mx;
975
32.5k
  hb_paint_normalize_color_line (stops.arrayZ, stops.length, &mn, &mx);
976
32.5k
  float gx0 = x0 + mn * (x1 - x0);
977
32.5k
  float gy0 = y0 + mn * (y1 - y0);
978
32.5k
  float gx1 = x0 + mx * (x1 - x0);
979
32.5k
  float gy1 = y0 + mx * (y1 - y0);
980
981
32.5k
  unsigned func_id = hb_pdf_build_gradient_function_from_stops (res, paint);
982
983
32.5k
  hb_paint_extend_t extend = hb_color_line_get_extend (color_line);
984
32.5k
  const char *extend_str = (extend == HB_PAINT_EXTEND_PAD)
985
32.5k
          ? "/Extend [true true]\n" : "";
986
987
  /* Build Type 2 (axial) shading — color only. */
988
32.5k
  hb_vector_buf_t sh;
989
32.5k
  sh.append_str ("<< /ShadingType 2 /ColorSpace /DeviceRGB\n");
990
32.5k
  sh.append_str ("/Coords [");
991
32.5k
  sh.append_num (paint->sx (gx0));
992
32.5k
  sh.append_c (' ');
993
32.5k
  sh.append_num (paint->sy (gy0));
994
32.5k
  sh.append_c (' ');
995
32.5k
  sh.append_num (paint->sx (gx1));
996
32.5k
  sh.append_c (' ');
997
32.5k
  sh.append_num (paint->sy (gy1));
998
32.5k
  sh.append_str ("]\n/Function ");
999
32.5k
  sh.append_unsigned (func_id);
1000
32.5k
  sh.append_str (" 0 R\n");
1001
32.5k
  sh.append_str (extend_str);
1002
32.5k
  sh.append_str (">>");
1003
1004
32.5k
  unsigned sh_idx = res->add_shading (std::move (sh));
1005
1006
32.5k
  auto &body = paint->current_body ();
1007
1008
32.5k
  bool needs_alpha = hb_pdf_gradient_needs_alpha (stops);
1009
32.5k
  if (needs_alpha)
1010
12.2k
  {
1011
12.2k
    unsigned alpha_func_id = hb_pdf_build_alpha_gradient_function_from_stops (res, paint);
1012
1013
12.2k
    hb_vector_buf_t ash;
1014
12.2k
    ash.append_str ("<< /ShadingType 2 /ColorSpace /DeviceGray\n");
1015
12.2k
    ash.append_str ("/Coords [");
1016
12.2k
    ash.append_num (paint->sx (gx0));
1017
12.2k
    ash.append_c (' ');
1018
12.2k
    ash.append_num (paint->sy (gy0));
1019
12.2k
    ash.append_c (' ');
1020
12.2k
    ash.append_num (paint->sx (gx1));
1021
12.2k
    ash.append_c (' ');
1022
12.2k
    ash.append_num (paint->sy (gy1));
1023
12.2k
    ash.append_str ("]\n/Function ");
1024
12.2k
    ash.append_unsigned (alpha_func_id);
1025
12.2k
    ash.append_str (" 0 R\n");
1026
12.2k
    ash.append_str (extend_str);
1027
12.2k
    ash.append_str (">>");
1028
12.2k
    unsigned alpha_sh_id = res->add_object (std::move (ash));
1029
1030
12.2k
    unsigned gs_idx = res->add_extgstate_smask (alpha_sh_id,
1031
12.2k
            paint->sx (gx0), paint->sy (gy0),
1032
12.2k
            paint->sx (gx1 - gx0), paint->sy (gy1 - gy0),
1033
12.2k
            paint->get_precision ());
1034
12.2k
    body.append_str ("/GS");
1035
12.2k
    body.append_unsigned (gs_idx);
1036
12.2k
    body.append_str (" gs\n");
1037
12.2k
  }
1038
1039
32.5k
  body.append_str ("/SH");
1040
32.5k
  body.append_unsigned (sh_idx);
1041
32.5k
  body.append_str (" sh\n");
1042
32.5k
}
1043
1044
static void
1045
hb_pdf_paint_radial_gradient (hb_paint_funcs_t *,
1046
            void *paint_data,
1047
            hb_color_line_t *color_line,
1048
            float x0, float y0, float r0,
1049
            float x1, float y1, float r1,
1050
            void *)
1051
47.2k
{
1052
47.2k
  auto *paint = (hb_vector_paint_t *) paint_data;
1053
47.2k
  if (unlikely (!paint->ensure_initialized ()))
1054
12.9k
    return;
1055
34.2k
  auto *res = hb_pdf_get_resources (paint);
1056
34.2k
  if (unlikely (!res))
1057
2.27k
    return;
1058
1059
  /* Fetch, normalize stops to [0,1], and adjust coordinates. */
1060
31.9k
  if (!paint->fetch_color_stops (color_line))
1061
4.14k
    return;
1062
27.8k
  hb_vector_t<hb_color_stop_t> &stops = paint->color_stops_scratch;
1063
1064
27.8k
  float mn, mx;
1065
27.8k
  hb_paint_normalize_color_line (stops.arrayZ, stops.length, &mn, &mx);
1066
27.8k
  float gx0 = x0 + mn * (x1 - x0);
1067
27.8k
  float gy0 = y0 + mn * (y1 - y0);
1068
27.8k
  float gr0 = r0 + mn * (r1 - r0);
1069
27.8k
  float gx1 = x0 + mx * (x1 - x0);
1070
27.8k
  float gy1 = y0 + mx * (y1 - y0);
1071
27.8k
  float gr1 = r0 + mx * (r1 - r0);
1072
1073
27.8k
  unsigned func_id = hb_pdf_build_gradient_function_from_stops (res, paint);
1074
1075
27.8k
  hb_paint_extend_t extend = hb_color_line_get_extend (color_line);
1076
27.8k
  const char *extend_str = (extend == HB_PAINT_EXTEND_PAD)
1077
27.8k
          ? "/Extend [true true]\n" : "";
1078
1079
  /* Build Type 3 (radial) shading — color only. */
1080
27.8k
  hb_vector_buf_t sh;
1081
27.8k
  sh.append_str ("<< /ShadingType 3 /ColorSpace /DeviceRGB\n");
1082
27.8k
  sh.append_str ("/Coords [");
1083
27.8k
  sh.append_num (paint->sx (gx0));
1084
27.8k
  sh.append_c (' ');
1085
27.8k
  sh.append_num (paint->sy (gy0));
1086
27.8k
  sh.append_c (' ');
1087
27.8k
  sh.append_num (paint->sx (gr0));
1088
27.8k
  sh.append_c (' ');
1089
27.8k
  sh.append_num (paint->sx (gx1));
1090
27.8k
  sh.append_c (' ');
1091
27.8k
  sh.append_num (paint->sy (gy1));
1092
27.8k
  sh.append_c (' ');
1093
27.8k
  sh.append_num (paint->sx (gr1));
1094
27.8k
  sh.append_str ("]\n/Function ");
1095
27.8k
  sh.append_unsigned (func_id);
1096
27.8k
  sh.append_str (" 0 R\n");
1097
27.8k
  sh.append_str (extend_str);
1098
27.8k
  sh.append_str (">>");
1099
1100
27.8k
  unsigned sh_idx = res->add_shading (std::move (sh));
1101
1102
27.8k
  auto &body = paint->current_body ();
1103
1104
27.8k
  bool needs_alpha = hb_pdf_gradient_needs_alpha (stops);
1105
27.8k
  if (needs_alpha)
1106
8.72k
  {
1107
8.72k
    unsigned alpha_func_id = hb_pdf_build_alpha_gradient_function_from_stops (res, paint);
1108
1109
8.72k
    hb_vector_buf_t ash;
1110
8.72k
    ash.append_str ("<< /ShadingType 3 /ColorSpace /DeviceGray\n");
1111
8.72k
    ash.append_str ("/Coords [");
1112
8.72k
    ash.append_num (paint->sx (gx0));
1113
8.72k
    ash.append_c (' ');
1114
8.72k
    ash.append_num (paint->sy (gy0));
1115
8.72k
    ash.append_c (' ');
1116
8.72k
    ash.append_num (paint->sx (gr0));
1117
8.72k
    ash.append_c (' ');
1118
8.72k
    ash.append_num (paint->sx (gx1));
1119
8.72k
    ash.append_c (' ');
1120
8.72k
    ash.append_num (paint->sy (gy1));
1121
8.72k
    ash.append_c (' ');
1122
8.72k
    ash.append_num (paint->sx (gr1));
1123
8.72k
    ash.append_str ("]\n/Function ");
1124
8.72k
    ash.append_unsigned (alpha_func_id);
1125
8.72k
    ash.append_str (" 0 R\n");
1126
8.72k
    ash.append_str (extend_str);
1127
8.72k
    ash.append_str (">>");
1128
8.72k
    unsigned alpha_sh_id = res->add_object (std::move (ash));
1129
1130
    /* BBox: enclosing square of the outer circle. */
1131
8.72k
    float cx = (gr1 >= gr0) ? gx1 : gx0;
1132
8.72k
    float cy = (gr1 >= gr0) ? gy1 : gy0;
1133
8.72k
    float rr = hb_max (gr0, gr1);
1134
8.72k
    unsigned gs_idx = res->add_extgstate_smask (alpha_sh_id,
1135
8.72k
            paint->sx (cx - rr), paint->sy (cy - rr),
1136
8.72k
            paint->sx (2 * rr), paint->sy (2 * rr),
1137
8.72k
            paint->get_precision ());
1138
8.72k
    body.append_str ("/GS");
1139
8.72k
    body.append_unsigned (gs_idx);
1140
8.72k
    body.append_str (" gs\n");
1141
8.72k
  }
1142
1143
27.8k
  body.append_str ("/SH");
1144
27.8k
  body.append_unsigned (sh_idx);
1145
27.8k
  body.append_str (" sh\n");
1146
27.8k
}
1147
1148
1149
/* Encode a 16-bit big-endian unsigned value into buf. */
1150
static void
1151
hb_pdf_encode_u16 (hb_vector_buf_t *buf, uint16_t v)
1152
136M
{
1153
136M
  char bytes[2] = {(char) (v >> 8), (char) (v & 0xFF)};
1154
136M
  buf->append_len (bytes, 2);
1155
136M
}
1156
1157
/* Encode a coordinate as 16-bit value relative to Decode range. */
1158
static void
1159
hb_pdf_encode_coord (hb_vector_buf_t *buf,
1160
         float val, float lo, float hi)
1161
136M
{
1162
136M
  float t = (val - lo) / (hi - lo);
1163
136M
  t = hb_clamp (t, 0.f, 1.f);
1164
136M
  hb_pdf_encode_u16 (buf, (uint16_t) (t * 65535.f + 0.5f));
1165
136M
}
1166
1167
/* Encode RGB from hb_color_t as 3 bytes. */
1168
static void
1169
hb_pdf_encode_color_rgb (hb_vector_buf_t *buf, hb_color_t c)
1170
11.3M
{
1171
11.3M
  char rgb[3] = {(char) hb_color_get_red (c),
1172
11.3M
     (char) hb_color_get_green (c),
1173
11.3M
     (char) hb_color_get_blue (c)};
1174
11.3M
  buf->append_len (rgb, 3);
1175
11.3M
}
1176
1177
/* Encode alpha from hb_color_t as 1 byte (gray). */
1178
static void
1179
hb_pdf_encode_color_alpha (hb_vector_buf_t *buf, hb_color_t c)
1180
11.3M
{
1181
11.3M
  char a = (char) hb_color_get_alpha (c);
1182
11.3M
  buf->append_len (&a, 1);
1183
11.3M
}
1184
1185
/* Encode one Coons patch control point. */
1186
static void
1187
hb_pdf_encode_point (hb_vector_buf_t *buf,
1188
         float x, float y,
1189
         float xlo, float xhi,
1190
         float ylo, float yhi)
1191
68.2M
{
1192
68.2M
  hb_pdf_encode_coord (buf, x, xlo, xhi);
1193
68.2M
  hb_pdf_encode_coord (buf, y, ylo, yhi);
1194
68.2M
}
1195
1196
/* Emit one Coons patch sector into the mesh stream(s).
1197
 * Splits large arcs into sub-patches of max 90°.
1198
 * If alpha_mesh is non-null, emits a parallel DeviceGray
1199
 * patch with the alpha channel. */
1200
static void
1201
hb_pdf_add_sweep_patch (hb_vector_buf_t *mesh,
1202
      hb_vector_buf_t *alpha_mesh,
1203
      float cx, float cy,
1204
      float xlo, float xhi, float ylo, float yhi,
1205
      float a0, hb_color_t c0_in,
1206
      float a1, hb_color_t c1_in)
1207
2.84M
{
1208
2.84M
  const float R = 32767.f;
1209
2.84M
  const float eps = 0.5f;
1210
2.84M
  const float MAX_SECTOR = (float) M_PI / 2.f;
1211
1212
2.84M
  int num_splits = (int) ceilf (fabsf (a1 - a0) / MAX_SECTOR);
1213
2.84M
  if (num_splits < 1) num_splits = 1;
1214
1215
5.69M
  for (int s = 0; s < num_splits; s++)
1216
2.84M
  {
1217
2.84M
    float k0 = (float) s / num_splits;
1218
2.84M
    float k1 = (float) (s + 1) / num_splits;
1219
2.84M
    float sa0 = a0 + k0 * (a1 - a0);
1220
2.84M
    float sa1 = a0 + k1 * (a1 - a0);
1221
2.84M
    hb_color_t sc0 = hb_color_lerp (c0_in, c1_in, k0);
1222
2.84M
    hb_color_t sc1 = hb_color_lerp (c0_in, c1_in, k1);
1223
1224
2.84M
    float da = sa1 - sa0;
1225
2.84M
    float kappa = (4.f / 3.f) * tanf (da / 4.f);
1226
1227
2.84M
    float cos0 = cosf (sa0), sin0 = sinf (sa0);
1228
2.84M
    float cos1 = cosf (sa1), sin1 = sinf (sa1);
1229
1230
2.84M
    float p0x = cx + eps * cos0, p0y = cy + eps * sin0;
1231
2.84M
    float p3x = cx + R * cos0,   p3y = cy + R * sin0;
1232
2.84M
    float p6x = cx + R * cos1,   p6y = cy + R * sin1;
1233
2.84M
    float p9x = cx + eps * cos1, p9y = cy + eps * sin1;
1234
1235
    /* Edge 1: p0→p3, radial straight line. */
1236
2.84M
    float e1_1x = p0x + (p3x - p0x) / 3.f;
1237
2.84M
    float e1_1y = p0y + (p3y - p0y) / 3.f;
1238
2.84M
    float e1_2x = p0x + 2.f * (p3x - p0x) / 3.f;
1239
2.84M
    float e1_2y = p0y + 2.f * (p3y - p0y) / 3.f;
1240
1241
    /* Edge 2: p3→p6, outer arc. */
1242
2.84M
    float e2_1x = p3x + kappa * R * (-sin0);
1243
2.84M
    float e2_1y = p3y + kappa * R * ( cos0);
1244
2.84M
    float e2_2x = p6x - kappa * R * (-sin1);
1245
2.84M
    float e2_2y = p6y - kappa * R * ( cos1);
1246
1247
    /* Edge 3: p6→p9, radial straight line. */
1248
2.84M
    float e3_1x = p6x + (p9x - p6x) / 3.f;
1249
2.84M
    float e3_1y = p6y + (p9y - p6y) / 3.f;
1250
2.84M
    float e3_2x = p6x + 2.f * (p9x - p6x) / 3.f;
1251
2.84M
    float e3_2y = p6y + 2.f * (p9y - p6y) / 3.f;
1252
1253
    /* Edge 4: p9→p0, inner arc. */
1254
2.84M
    float e4_1x = p9x + kappa * eps * (-sin1);
1255
2.84M
    float e4_1y = p9y + kappa * eps * ( cos1);
1256
2.84M
    float e4_2x = p0x - kappa * eps * (-sin0);
1257
2.84M
    float e4_2y = p0y - kappa * eps * ( cos0);
1258
1259
2.84M
    mesh->append_c ('\0'); /* flag = 0, new patch */
1260
1261
2.84M
    hb_pdf_encode_point (mesh, p0x, p0y, xlo, xhi, ylo, yhi);
1262
2.84M
    hb_pdf_encode_point (mesh, e1_1x, e1_1y, xlo, xhi, ylo, yhi);
1263
2.84M
    hb_pdf_encode_point (mesh, e1_2x, e1_2y, xlo, xhi, ylo, yhi);
1264
2.84M
    hb_pdf_encode_point (mesh, p3x, p3y, xlo, xhi, ylo, yhi);
1265
2.84M
    hb_pdf_encode_point (mesh, e2_1x, e2_1y, xlo, xhi, ylo, yhi);
1266
2.84M
    hb_pdf_encode_point (mesh, e2_2x, e2_2y, xlo, xhi, ylo, yhi);
1267
2.84M
    hb_pdf_encode_point (mesh, p6x, p6y, xlo, xhi, ylo, yhi);
1268
2.84M
    hb_pdf_encode_point (mesh, e3_1x, e3_1y, xlo, xhi, ylo, yhi);
1269
2.84M
    hb_pdf_encode_point (mesh, e3_2x, e3_2y, xlo, xhi, ylo, yhi);
1270
2.84M
    hb_pdf_encode_point (mesh, p9x, p9y, xlo, xhi, ylo, yhi);
1271
2.84M
    hb_pdf_encode_point (mesh, e4_1x, e4_1y, xlo, xhi, ylo, yhi);
1272
2.84M
    hb_pdf_encode_point (mesh, e4_2x, e4_2y, xlo, xhi, ylo, yhi);
1273
1274
2.84M
    hb_pdf_encode_color_rgb (mesh, sc0); /* inner start */
1275
2.84M
    hb_pdf_encode_color_rgb (mesh, sc0); /* outer start */
1276
2.84M
    hb_pdf_encode_color_rgb (mesh, sc1); /* outer end */
1277
2.84M
    hb_pdf_encode_color_rgb (mesh, sc1); /* inner end */
1278
1279
2.84M
    if (alpha_mesh)
1280
2.84M
    {
1281
2.84M
      alpha_mesh->append_c ('\0');
1282
1283
2.84M
      hb_pdf_encode_point (alpha_mesh, p0x, p0y, xlo, xhi, ylo, yhi);
1284
2.84M
      hb_pdf_encode_point (alpha_mesh, e1_1x, e1_1y, xlo, xhi, ylo, yhi);
1285
2.84M
      hb_pdf_encode_point (alpha_mesh, e1_2x, e1_2y, xlo, xhi, ylo, yhi);
1286
2.84M
      hb_pdf_encode_point (alpha_mesh, p3x, p3y, xlo, xhi, ylo, yhi);
1287
2.84M
      hb_pdf_encode_point (alpha_mesh, e2_1x, e2_1y, xlo, xhi, ylo, yhi);
1288
2.84M
      hb_pdf_encode_point (alpha_mesh, e2_2x, e2_2y, xlo, xhi, ylo, yhi);
1289
2.84M
      hb_pdf_encode_point (alpha_mesh, p6x, p6y, xlo, xhi, ylo, yhi);
1290
2.84M
      hb_pdf_encode_point (alpha_mesh, e3_1x, e3_1y, xlo, xhi, ylo, yhi);
1291
2.84M
      hb_pdf_encode_point (alpha_mesh, e3_2x, e3_2y, xlo, xhi, ylo, yhi);
1292
2.84M
      hb_pdf_encode_point (alpha_mesh, p9x, p9y, xlo, xhi, ylo, yhi);
1293
2.84M
      hb_pdf_encode_point (alpha_mesh, e4_1x, e4_1y, xlo, xhi, ylo, yhi);
1294
2.84M
      hb_pdf_encode_point (alpha_mesh, e4_2x, e4_2y, xlo, xhi, ylo, yhi);
1295
1296
2.84M
      hb_pdf_encode_color_alpha (alpha_mesh, sc0);
1297
2.84M
      hb_pdf_encode_color_alpha (alpha_mesh, sc0);
1298
2.84M
      hb_pdf_encode_color_alpha (alpha_mesh, sc1);
1299
2.84M
      hb_pdf_encode_color_alpha (alpha_mesh, sc1);
1300
2.84M
    }
1301
2.84M
  }
1302
2.84M
}
1303
1304
/* Callback context + trampoline for hb_paint_sweep_gradient_tiles. */
1305
struct hb_pdf_sweep_ctx_t {
1306
  hb_vector_buf_t *mesh;
1307
  hb_vector_buf_t *alpha_mesh;
1308
  float cx, cy, xlo, xhi, ylo, yhi;
1309
};
1310
1311
static void
1312
hb_pdf_sweep_emit_patch (float a0, hb_color_t c0,
1313
       float a1, hb_color_t c1,
1314
       void *user_data)
1315
2.84M
{
1316
2.84M
  auto *ctx = (hb_pdf_sweep_ctx_t *) user_data;
1317
2.84M
  hb_pdf_add_sweep_patch (ctx->mesh, ctx->alpha_mesh,
1318
2.84M
        ctx->cx, ctx->cy,
1319
2.84M
        ctx->xlo, ctx->xhi, ctx->ylo, ctx->yhi,
1320
2.84M
        a0, c0, a1, c1);
1321
2.84M
}
1322
1323
static void
1324
hb_pdf_paint_sweep_gradient (hb_paint_funcs_t *,
1325
           void *paint_data,
1326
           hb_color_line_t *color_line,
1327
           float cx, float cy,
1328
           float start_angle,
1329
           float end_angle,
1330
           void *)
1331
4.82k
{
1332
4.82k
  auto *paint = (hb_vector_paint_t *) paint_data;
1333
4.82k
  if (unlikely (!paint->ensure_initialized ()))
1334
1.21k
    return;
1335
3.61k
  auto *res = hb_pdf_get_resources (paint);
1336
3.61k
  if (unlikely (!res))
1337
10
    return;
1338
1339
  /* Get and sort color stops. */
1340
3.60k
  if (!paint->fetch_color_stops (color_line))
1341
920
    return;
1342
2.68k
  hb_vector_t<hb_color_stop_t> &stops = paint->color_stops_scratch;
1343
2.68k
  stops.as_array ().qsort (
1344
2.68k
    [] (const hb_color_stop_t &a, const hb_color_stop_t &b)
1345
678k
    { return (a.offset > b.offset) - (a.offset < b.offset); });
1346
1347
2.68k
  hb_paint_extend_t extend = hb_color_line_get_extend (color_line);
1348
1349
2.68k
  const float R = 32767.f;
1350
2.68k
  float scx = paint->sx (cx), scy = paint->sy (cy);
1351
2.68k
  float xlo = scx - R - 1, xhi = scx + R + 1;
1352
2.68k
  float ylo = scy - R - 1, yhi = scy + R + 1;
1353
1354
2.68k
  bool needs_alpha = hb_pdf_gradient_needs_alpha (stops);
1355
1356
2.68k
  hb_vector_buf_t mesh;
1357
2.68k
  hb_vector_buf_t alpha_mesh;
1358
2.68k
  mesh.alloc (256);
1359
2.68k
  if (needs_alpha)
1360
1.96k
    alpha_mesh.alloc (256);
1361
1362
2.68k
  hb_pdf_sweep_ctx_t ctx { &mesh, needs_alpha ? &alpha_mesh : nullptr,
1363
2.68k
          scx, scy, xlo, xhi, ylo, yhi };
1364
2.68k
  hb_paint_sweep_gradient_tiles (stops.arrayZ, stops.length, extend,
1365
2.68k
         start_angle, end_angle,
1366
2.68k
         hb_pdf_sweep_emit_patch, &ctx);
1367
1368
2.68k
  if (!mesh.length)
1369
256
    return;
1370
1371
2.43k
  auto hb_pdf_build_mesh_shading = [&] (hb_vector_buf_t &m,
1372
2.43k
           const char *cs,
1373
2.43k
           const char *decode_suffix) -> unsigned
1374
4.12k
  {
1375
4.12k
    hb_vector_buf_t sh;
1376
4.12k
    sh.append_str ("<< /ShadingType 6 /ColorSpace /");
1377
4.12k
    sh.append_str (cs);
1378
4.12k
    sh.append_str ("\n/BitsPerCoordinate 16 /BitsPerComponent 8 /BitsPerFlag 8\n");
1379
4.12k
    sh.append_str ("/Decode [");
1380
4.12k
    sh.append_num (xlo, 2);
1381
4.12k
    sh.append_c (' ');
1382
4.12k
    sh.append_num (xhi, 2);
1383
4.12k
    sh.append_c (' ');
1384
4.12k
    sh.append_num (ylo, 2);
1385
4.12k
    sh.append_c (' ');
1386
4.12k
    sh.append_num (yhi, 2);
1387
4.12k
    sh.append_str (decode_suffix);
1388
4.12k
    sh.append_str ("]\n/Length ");
1389
4.12k
    sh.append_unsigned (m.length);
1390
4.12k
    sh.append_str (" >>\nstream\n");
1391
4.12k
    sh.append_len (m.arrayZ, m.length);
1392
4.12k
    sh.append_str ("\nendstream");
1393
4.12k
    return res->add_object (std::move (sh));
1394
4.12k
  };
1395
1396
2.43k
  unsigned sh_obj_id = hb_pdf_build_mesh_shading (mesh, "DeviceRGB",
1397
2.43k
               " 0 1 0 1 0 1");
1398
2.43k
  unsigned sh_idx = res->add_shading_by_id (sh_obj_id);
1399
1400
2.43k
  auto &body = paint->current_body ();
1401
1402
2.43k
  if (needs_alpha && alpha_mesh.length)
1403
1.69k
  {
1404
1.69k
    unsigned alpha_sh_id = hb_pdf_build_mesh_shading (alpha_mesh, "DeviceGray",
1405
1.69k
                   " 0 1");
1406
1.69k
    unsigned gs_idx = res->add_extgstate_smask (alpha_sh_id,
1407
1.69k
            xlo, ylo,
1408
1.69k
            xhi - xlo, yhi - ylo,
1409
1.69k
            paint->get_precision ());
1410
1.69k
    body.append_str ("/GS");
1411
1.69k
    body.append_unsigned (gs_idx);
1412
1.69k
    body.append_str (" gs\n");
1413
1.69k
  }
1414
1415
2.43k
  body.append_str ("/SH");
1416
2.43k
  body.append_unsigned (sh_idx);
1417
2.43k
  body.append_str (" sh\n");
1418
2.43k
}
1419
1420
static const char *
1421
hb_pdf_blend_mode_name (hb_paint_composite_mode_t mode)
1422
17.0k
{
1423
17.0k
  switch (mode)
1424
17.0k
  {
1425
252
  case HB_PAINT_COMPOSITE_MODE_MULTIPLY:       return "Multiply";
1426
58
  case HB_PAINT_COMPOSITE_MODE_SCREEN:         return "Screen";
1427
73
  case HB_PAINT_COMPOSITE_MODE_OVERLAY:        return "Overlay";
1428
42
  case HB_PAINT_COMPOSITE_MODE_DARKEN:         return "Darken";
1429
226
  case HB_PAINT_COMPOSITE_MODE_LIGHTEN:        return "Lighten";
1430
98
  case HB_PAINT_COMPOSITE_MODE_COLOR_DODGE:    return "ColorDodge";
1431
70
  case HB_PAINT_COMPOSITE_MODE_COLOR_BURN:     return "ColorBurn";
1432
70
  case HB_PAINT_COMPOSITE_MODE_HARD_LIGHT:     return "HardLight";
1433
70
  case HB_PAINT_COMPOSITE_MODE_SOFT_LIGHT:     return "SoftLight";
1434
72
  case HB_PAINT_COMPOSITE_MODE_DIFFERENCE:     return "Difference";
1435
66
  case HB_PAINT_COMPOSITE_MODE_EXCLUSION:      return "Exclusion";
1436
308
  case HB_PAINT_COMPOSITE_MODE_HSL_HUE:        return "Hue";
1437
224
  case HB_PAINT_COMPOSITE_MODE_HSL_SATURATION: return "Saturation";
1438
223
  case HB_PAINT_COMPOSITE_MODE_HSL_COLOR:      return "Color";
1439
202
  case HB_PAINT_COMPOSITE_MODE_HSL_LUMINOSITY: return "Luminosity";
1440
  /* Porter-Duff modes have no PDF blend-mode equivalent; approximate
1441
   * the two that have a plausible color-blend analog, and let the
1442
   * rest fall through to Normal (SRC_OVER). */
1443
47
  case HB_PAINT_COMPOSITE_MODE_PLUS:           return "Screen";
1444
38
  case HB_PAINT_COMPOSITE_MODE_XOR:            return "Difference";
1445
4.74k
  case HB_PAINT_COMPOSITE_MODE_CLEAR:
1446
4.92k
  case HB_PAINT_COMPOSITE_MODE_SRC:
1447
5.23k
  case HB_PAINT_COMPOSITE_MODE_DEST:
1448
13.9k
  case HB_PAINT_COMPOSITE_MODE_SRC_OVER:
1449
14.1k
  case HB_PAINT_COMPOSITE_MODE_DEST_OVER:
1450
14.3k
  case HB_PAINT_COMPOSITE_MODE_SRC_IN:
1451
14.4k
  case HB_PAINT_COMPOSITE_MODE_DEST_IN:
1452
14.6k
  case HB_PAINT_COMPOSITE_MODE_SRC_OUT:
1453
14.8k
  case HB_PAINT_COMPOSITE_MODE_DEST_OUT:
1454
14.8k
  case HB_PAINT_COMPOSITE_MODE_SRC_ATOP:
1455
14.8k
  case HB_PAINT_COMPOSITE_MODE_DEST_ATOP:
1456
14.8k
  default:                                     return nullptr; /* Normal */
1457
17.0k
  }
1458
17.0k
}
1459
1460
static void
1461
hb_pdf_paint_push_group (hb_paint_funcs_t *,
1462
       void *paint_data,
1463
       void *)
1464
0
{
1465
0
  auto *paint = (hb_vector_paint_t *) paint_data;
1466
0
  if (unlikely (!paint->ensure_initialized ()))
1467
0
    return;
1468
0
  paint->current_body ().append_str ("q\n");
1469
0
}
1470
1471
static void
1472
hb_pdf_paint_push_group_for (hb_paint_funcs_t *,
1473
           void *paint_data,
1474
           hb_paint_composite_mode_t mode,
1475
           void *)
1476
25.0k
{
1477
25.0k
  auto *paint = (hb_vector_paint_t *) paint_data;
1478
25.0k
  if (unlikely (!paint->ensure_initialized ()))
1479
8.03k
    return;
1480
1481
17.0k
  auto &body = paint->current_body ();
1482
17.0k
  body.append_str ("q\n");
1483
1484
17.0k
  const char *bm = hb_pdf_blend_mode_name (mode);
1485
17.0k
  if (bm)
1486
2.13k
  {
1487
2.13k
    auto *res = hb_pdf_get_resources (paint);
1488
2.13k
    if (likely (res))
1489
1.95k
    {
1490
1.95k
      unsigned gs_idx = res->add_extgstate_blend (bm);
1491
1.95k
      body.append_str ("/GS");
1492
1.95k
      body.append_unsigned (gs_idx);
1493
1.95k
      body.append_str (" gs\n");
1494
1.95k
    }
1495
2.13k
  }
1496
17.0k
}
1497
1498
static void
1499
hb_pdf_paint_pop_group (hb_paint_funcs_t *,
1500
      void *paint_data,
1501
      hb_paint_composite_mode_t mode HB_UNUSED,
1502
      void *)
1503
25.0k
{
1504
25.0k
  auto *paint = (hb_vector_paint_t *) paint_data;
1505
25.0k
  if (unlikely (!paint->ensure_initialized ()))
1506
8.05k
    return;
1507
16.9k
  paint->current_body ().append_str ("Q\n");
1508
16.9k
}
1509
1510
1511
/* ---- lazy loader for paint funcs ---- */
1512
1513
static inline void free_static_pdf_paint_funcs ();
1514
1515
static struct hb_pdf_paint_funcs_lazy_loader_t
1516
  : hb_paint_funcs_lazy_loader_t<hb_pdf_paint_funcs_lazy_loader_t>
1517
{
1518
  static hb_paint_funcs_t *create ()
1519
1
  {
1520
1
    hb_paint_funcs_t *funcs = hb_paint_funcs_create ();
1521
1
    hb_paint_funcs_set_push_transform_func (funcs, (hb_paint_push_transform_func_t) hb_pdf_paint_push_transform, nullptr, nullptr);
1522
1
    hb_paint_funcs_set_pop_transform_func (funcs, (hb_paint_pop_transform_func_t) hb_pdf_paint_pop_transform, nullptr, nullptr);
1523
1
    hb_paint_funcs_set_push_clip_glyph_func (funcs, (hb_paint_push_clip_glyph_func_t) hb_pdf_paint_push_clip_glyph, nullptr, nullptr);
1524
1
    hb_paint_funcs_set_push_clip_rectangle_func (funcs, (hb_paint_push_clip_rectangle_func_t) hb_pdf_paint_push_clip_rectangle, nullptr, nullptr);
1525
1
    hb_paint_funcs_set_push_clip_path_start_func (funcs, (hb_paint_push_clip_path_start_func_t) hb_pdf_paint_push_clip_path_start, nullptr, nullptr);
1526
1
    hb_paint_funcs_set_push_clip_path_end_func (funcs, (hb_paint_push_clip_path_end_func_t) hb_pdf_paint_push_clip_path_end, nullptr, nullptr);
1527
1
    hb_paint_funcs_set_pop_clip_func (funcs, (hb_paint_pop_clip_func_t) hb_pdf_paint_pop_clip, nullptr, nullptr);
1528
1
    hb_paint_funcs_set_color_func (funcs, (hb_paint_color_func_t) hb_pdf_paint_color, nullptr, nullptr);
1529
1
    hb_paint_funcs_set_image_func (funcs, (hb_paint_image_func_t) hb_pdf_paint_image, nullptr, nullptr);
1530
1
    hb_paint_funcs_set_linear_gradient_func (funcs, (hb_paint_linear_gradient_func_t) hb_pdf_paint_linear_gradient, nullptr, nullptr);
1531
1
    hb_paint_funcs_set_radial_gradient_func (funcs, (hb_paint_radial_gradient_func_t) hb_pdf_paint_radial_gradient, nullptr, nullptr);
1532
1
    hb_paint_funcs_set_sweep_gradient_func (funcs, (hb_paint_sweep_gradient_func_t) hb_pdf_paint_sweep_gradient, nullptr, nullptr);
1533
1
    hb_paint_funcs_set_push_group_func (funcs, (hb_paint_push_group_func_t) hb_pdf_paint_push_group, nullptr, nullptr);
1534
1
    hb_paint_funcs_set_push_group_for_func (funcs, (hb_paint_push_group_for_func_t) hb_pdf_paint_push_group_for, nullptr, nullptr);
1535
1
    hb_paint_funcs_set_pop_group_func (funcs, (hb_paint_pop_group_func_t) hb_pdf_paint_pop_group, nullptr, nullptr);
1536
1
    hb_paint_funcs_make_immutable (funcs);
1537
1
    hb_atexit (free_static_pdf_paint_funcs);
1538
1
    return funcs;
1539
1
  }
1540
} static_pdf_paint_funcs;
1541
1542
static inline void
1543
free_static_pdf_paint_funcs ()
1544
1
{
1545
1
  static_pdf_paint_funcs.free_instance ();
1546
1
}
1547
1548
hb_paint_funcs_t *
1549
hb_vector_paint_pdf_funcs_get ()
1550
164k
{
1551
164k
  return static_pdf_paint_funcs.get_unconst ();
1552
164k
}
1553
1554
1555
/* ---- render ---- */
1556
1557
hb_blob_t *
1558
hb_vector_paint_render_pdf (hb_vector_paint_t *paint)
1559
27.2k
{
1560
27.2k
  if (!paint->has_extents)
1561
20.2k
    return nullptr;
1562
7.05k
  if (!paint->group_stack.length ||
1563
6.90k
      !paint->group_stack.arrayZ[0].length)
1564
151
    return nullptr;
1565
1566
6.90k
  hb_vector_buf_t &content = paint->group_stack.arrayZ[0];
1567
6.90k
  hb_pdf_resources_t *res = hb_pdf_get_resources (paint);
1568
1569
6.90k
  float ex = paint->extents.x;
1570
6.90k
  float ey = paint->extents.y;
1571
6.90k
  float ew = paint->extents.width;
1572
6.90k
  float eh = paint->extents.height;
1573
1574
6.90k
  unsigned num_extra = res ? res->objects.length : 0;
1575
6.90k
  unsigned total_objects = 4 + num_extra; /* 1-based: 1..total_objects */
1576
1577
  /* Build PDF. */
1578
6.90k
  hb_vector_buf_t out;
1579
6.90k
  hb_buf_recover_recycled (paint->recycled_blob, &out);
1580
6.90k
  out.alloc (content.length + num_extra * 128 + 1024);
1581
1582
6.90k
  hb_vector_t<unsigned> offsets;
1583
6.90k
  if (unlikely (!offsets.resize (total_objects)))
1584
123
    return nullptr;
1585
1586
6.78k
  out.append_str ("%PDF-1.4\n%\xC0\xC1\xC2\xC3\n");
1587
1588
  /* Object 1: Catalog */
1589
6.78k
  offsets.arrayZ[0] = out.length;
1590
6.78k
  out.append_str ("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
1591
1592
  /* Object 2: Pages */
1593
6.78k
  offsets.arrayZ[1] = out.length;
1594
6.78k
  out.append_str ("2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
1595
1596
  /* Object 3: Page */
1597
6.78k
  offsets.arrayZ[2] = out.length;
1598
6.78k
  out.append_str ("3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [");
1599
6.78k
  out.append_num (ex);
1600
6.78k
  out.append_c (' ');
1601
6.78k
  out.append_num (-(ey + eh));
1602
6.78k
  out.append_c (' ');
1603
6.78k
  out.append_num (ex + ew);
1604
6.78k
  out.append_c (' ');
1605
6.78k
  out.append_num (-ey);
1606
6.78k
  out.append_str ("]\n/Contents 4 0 R");
1607
1608
  /* Resources. */
1609
6.78k
  bool has_resources = res &&
1610
6.55k
    (res->extgstate_dict.length || res->shading_dict.length || res->xobject_dict.length);
1611
6.78k
  if (has_resources)
1612
836
  {
1613
836
    out.append_str ("\n/Resources <<");
1614
836
    if (res->extgstate_dict.length)
1615
778
    {
1616
778
      out.append_str (" /ExtGState << ");
1617
778
      out.append_len (res->extgstate_dict.arrayZ, res->extgstate_dict.length);
1618
778
      out.append_str (">>");
1619
778
    }
1620
836
    if (res->shading_dict.length)
1621
756
    {
1622
756
      out.append_str (" /Shading << ");
1623
756
      out.append_len (res->shading_dict.arrayZ, res->shading_dict.length);
1624
756
      out.append_str (">>");
1625
756
    }
1626
836
    if (res->xobject_dict.length)
1627
20
    {
1628
20
      out.append_str (" /XObject << ");
1629
20
      out.append_len (res->xobject_dict.arrayZ, res->xobject_dict.length);
1630
20
      out.append_str (">>");
1631
20
    }
1632
836
    out.append_str (" >>");
1633
836
  }
1634
1635
6.78k
  out.append_str (" >>\nendobj\n");
1636
1637
  /* Build content stream: optional background rect + glyph content. */
1638
6.78k
  hb_vector_buf_t bg_prefix;
1639
6.78k
  if (hb_color_get_alpha (paint->background))
1640
0
  {
1641
0
    float r = hb_color_get_red (paint->background) / 255.f;
1642
0
    float g = hb_color_get_green (paint->background) / 255.f;
1643
0
    float b = hb_color_get_blue (paint->background) / 255.f;
1644
0
    float a = hb_color_get_alpha (paint->background) / 255.f;
1645
0
    if (a < 1.f - 1.f / 512.f)
1646
0
    {
1647
0
      if (res)
1648
0
      {
1649
0
  unsigned gs_idx = res->add_extgstate_alpha (a);
1650
0
  bg_prefix.append_str ("/GS");
1651
0
  bg_prefix.append_unsigned (gs_idx);
1652
0
  bg_prefix.append_str (" gs\n");
1653
0
      }
1654
0
    }
1655
0
    bg_prefix.append_num (r, 4);
1656
0
    bg_prefix.append_c (' ');
1657
0
    bg_prefix.append_num (g, 4);
1658
0
    bg_prefix.append_c (' ');
1659
0
    bg_prefix.append_num (b, 4);
1660
0
    bg_prefix.append_str (" rg\n");
1661
0
    bg_prefix.append_num (ex);
1662
0
    bg_prefix.append_c (' ');
1663
0
    bg_prefix.append_num (-(ey + eh));
1664
0
    bg_prefix.append_c (' ');
1665
0
    bg_prefix.append_num (ew);
1666
0
    bg_prefix.append_c (' ');
1667
0
    bg_prefix.append_num (eh);
1668
0
    bg_prefix.append_str (" re f\n");
1669
0
  }
1670
6.78k
  unsigned stream_len = bg_prefix.length + content.length;
1671
1672
  /* Object 4: Content stream */
1673
6.78k
  offsets.arrayZ[3] = out.length;
1674
6.78k
  out.append_str ("4 0 obj\n<< /Length ");
1675
6.78k
  out.append_unsigned (stream_len);
1676
6.78k
  out.append_str (" >>\nstream\n");
1677
6.78k
  out.append_len (bg_prefix.arrayZ, bg_prefix.length);
1678
6.78k
  out.append_len (content.arrayZ, content.length);
1679
6.78k
  out.append_str ("endstream\nendobj\n");
1680
1681
  /* Extra objects (functions, shadings, ExtGState). */
1682
661k
  for (unsigned i = 0; i < num_extra; i++)
1683
654k
  {
1684
654k
    offsets.arrayZ[4 + i] = out.length;
1685
654k
    out.append_unsigned (5 + i);
1686
654k
    out.append_str (" 0 obj\n");
1687
654k
    auto &obj = res->objects.arrayZ[i];
1688
654k
    out.append_len (obj.data.arrayZ, obj.data.length);
1689
654k
    out.append_str ("\nendobj\n");
1690
654k
  }
1691
1692
  /* Cross-reference table */
1693
6.78k
  unsigned xref_offset = out.length;
1694
6.78k
  out.append_str ("xref\n0 ");
1695
6.78k
  out.append_unsigned (total_objects + 1);
1696
6.78k
  out.append_str ("\n0000000000 65535 f \n");
1697
688k
  for (unsigned i = 0; i < total_objects; i++)
1698
681k
  {
1699
681k
    char tmp[21];
1700
681k
    snprintf (tmp, sizeof (tmp), "%010u 00000 n \n", offsets.arrayZ[i]);
1701
681k
    out.append_len (tmp, 20);
1702
681k
  }
1703
1704
  /* Trailer */
1705
6.78k
  out.append_str ("trailer\n<< /Size ");
1706
6.78k
  out.append_unsigned (total_objects + 1);
1707
6.78k
  out.append_str (" /Root 1 0 R >>\nstartxref\n");
1708
6.78k
  out.append_unsigned (xref_offset);
1709
6.78k
  out.append_str ("\n%%EOF\n");
1710
1711
6.78k
  hb_blob_t *blob = hb_buf_blob_from (&paint->recycled_blob, &out);
1712
1713
6.78k
  hb_vector_paint_clear (paint);
1714
1715
6.78k
  return blob;
1716
6.90k
}