FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/textformat/tf_mermaid.c
Date: 2026-01-23 19:11:46
Exec Total Coverage
Lines: 0 288 0.0%
Functions: 0 12 0.0%
Branches: 0 170 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) The FFmpeg developers
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <limits.h>
22 #include <stdarg.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "avtextformat.h"
28 #include "tf_internal.h"
29 #include "tf_mermaid.h"
30 #include "libavutil/bprint.h"
31 #include "libavutil/mem.h"
32 #include "libavutil/opt.h"
33
34
35 static const char *init_directive = ""
36 "%%{init: {"
37 "\"theme\": \"base\","
38 "\"curve\": \"monotoneX\","
39 "\"rankSpacing\": 10,"
40 "\"nodeSpacing\": 10,"
41 "\"themeCSS\": \"__###__\","
42 "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
43 "\"themeVariables\": { "
44 "\"clusterBkg\": \"white\", "
45 "\"primaryBorderColor\": \"gray\", "
46 "\"lineColor\": \"gray\", "
47 "\"secondaryTextColor\": \"gray\", "
48 "\"tertiaryBorderColor\": \"gray\", "
49 "\"primaryTextColor\": \"#666\", "
50 "\"secondaryTextColor\": \"red\" "
51 "},"
52 "\"flowchart\": { "
53 "\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
54 "\"diagramPadding\": 20, "
55 "\"curve\": \"monotoneX\" "
56 "}"
57 " }}%%\n\n";
58
59 static const char* init_directive_er = ""
60 "%%{init: {"
61 "\"theme\": \"base\","
62 "\"layout\": \"elk\","
63 "\"curve\": \"monotoneX\","
64 "\"rankSpacing\": 65,"
65 "\"nodeSpacing\": 60,"
66 "\"themeCSS\": \"__###__\","
67 "\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
68 "\"themeVariables\": { "
69 "\"clusterBkg\": \"white\", "
70 "\"primaryBorderColor\": \"gray\", "
71 "\"lineColor\": \"gray\", "
72 "\"secondaryTextColor\": \"gray\", "
73 "\"tertiaryBorderColor\": \"gray\", "
74 "\"primaryTextColor\": \"#666\", "
75 "\"secondaryTextColor\": \"red\" "
76 "},"
77 "\"er\": { "
78 "\"diagramPadding\": 12, "
79 "\"entityPadding\": 4, "
80 "\"minEntityWidth\": 150, "
81 "\"minEntityHeight\": 20, "
82 "\"curve\": \"monotoneX\" "
83 "}"
84 " }}%%\n\n";
85
86 static const char *theme_css_er = ""
87
88 // Variables
89 ".root { "
90 "--ff-colvideo: #6eaa7b; "
91 "--ff-colaudio: #477fb3; "
92 "--ff-colsubtitle: #ad76ab; "
93 "--ff-coltext: #666; "
94 "} "
95 " g.nodes g.node.default rect.basic.label-container, "
96 " g.nodes g.node.default path { "
97 " rx: 1; "
98 " ry: 1; "
99 " stroke-width: 1px !important; "
100 " stroke: #e9e9e9 !important; "
101 " fill: url(#ff-filtergradient) !important; "
102 " filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
103 " fill: white !important; "
104 " } "
105 " "
106 " .relationshipLine { "
107 " stroke: gray; "
108 " stroke-width: 1; "
109 " fill: none; "
110 " filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
111 " } "
112 " "
113 " g.node.default g.label.name foreignObject > div > span > p, "
114 " g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
115 " font-size: 0.95rem; "
116 " font-weight: 500; "
117 " text-transform: uppercase; "
118 " min-width: 5.5rem; "
119 " margin-bottom: 0.5rem; "
120 " "
121 " } "
122 " "
123 " .edgePaths path { "
124 " marker-end: none; "
125 " marker-start: none; "
126 " "
127 "} ";
128
129
130 /* Mermaid Graph output */
131
132 typedef struct MermaidContext {
133 const AVClass *class;
134 AVDiagramConfig *diagram_config;
135 int subgraph_count;
136 int within_tag;
137 int indent_level;
138 int create_html;
139
140 // Options
141 int enable_link_colors; // Requires Mermaid 11.5
142
143 struct section_data {
144 const char *section_id;
145 const char *section_type;
146 const char *src_id;
147 const char *dest_id;
148 AVTextFormatLinkType link_type;
149 int current_is_textblock;
150 int current_is_stadium;
151 int subgraph_start_incomplete;
152 } section_data[SECTION_MAX_NB_LEVELS];
153
154 unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
155 AVBPrint link_buf; ///< print buffer for writing diagram links
156 AVDictionary *link_dict;
157 } MermaidContext;
158
159 #undef OFFSET
160 #define OFFSET(x) offsetof(MermaidContext, x)
161
162 static const AVOption mermaid_options[] = {
163 { "link_coloring", "enable colored links (requires Mermaid >= 11.5)", OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
164 ////{"diagram_css", "CSS for the diagram", OFFSET(diagram_css), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
165 ////{"html_template", "Template HTML", OFFSET(html_template), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
166 { NULL },
167 };
168
169 DEFINE_FORMATTER_CLASS(mermaid);
170
171 void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
172 {
173 MermaidContext *mmc = tfc->priv;
174 mmc->diagram_config = diagram_config;
175 }
176
177 static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
178 {
179 MermaidContext *mmc = tfc->priv;
180 AVBPrint buf;
181
182 av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
183 av_bprintf(&buf, "%s--%s", src, dest);
184
185 if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
186 return 1;
187
188 av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
189
190 return 0;
191 }
192
193 static av_cold int mermaid_init(AVTextFormatContext *tfc)
194 {
195 MermaidContext *mmc = tfc->priv;
196
197 av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
198
199 ////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
200 return 0;
201 }
202
203 static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
204 {
205 MermaidContext *mmc = tfc->priv;
206
207 int ret = mermaid_init(tfc);
208
209 if (ret < 0)
210 return ret;
211
212 mmc->create_html = 1;
213
214 return 0;
215 }
216
217 static av_cold int mermaid_uninit(AVTextFormatContext *tfc)
218 {
219 MermaidContext *mmc = tfc->priv;
220
221 av_bprint_finalize(&mmc->link_buf, NULL);
222 av_dict_free(&mmc->link_dict);
223
224 for (unsigned i = 0; i < SECTION_MAX_NB_LEVELS; i++) {
225 av_freep(&mmc->section_data[i].dest_id);
226 av_freep(&mmc->section_data[i].section_id);
227 av_freep(&mmc->section_data[i].src_id);
228 av_freep(&mmc->section_data[i].section_type);
229 }
230
231 return 0;
232 }
233
234 static void set_str(const char **dst, const char *src)
235 {
236 if (*dst)
237 av_freep(dst);
238
239 if (src)
240 *dst = av_strdup(src);
241 }
242
243 static void mermaid_subgraph_complete_start(MermaidContext *mmc, AVTextFormatContext *tfc, int level) {
244 struct section_data parent_sec_data = mmc->section_data[level];
245 AVBPrint *parent_buf = &tfc->section_pbuf[level];
246
247 if (parent_sec_data.subgraph_start_incomplete) {
248 if (parent_buf->len > 0)
249 writer_printf(tfc, "%s", parent_buf->str);
250
251 writer_put_str(tfc, "</div>\"]\n");
252
253 mmc->section_data[level].subgraph_start_incomplete = 0;
254 }
255 }
256
257 #define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
258
259 static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
260 {
261 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
262 const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
263
264 if (!section)
265 return;
266 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
267 MermaidContext *mmc = tfc->priv;
268 const AVTextFormatSectionContext *sec_ctx = data;
269
270 if (tfc->level == 0) {
271 char *directive;
272 AVBPrint css_buf;
273 const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
274 char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
275 (void)theme_css_er;
276 ////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
277 av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
278 av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
279 av_freep(&single_line_css);
280
281 directive = av_strireplace(diag_directive, "__###__", css_buf.str);
282 if (mmc->create_html) {
283 uint64_t length;
284 char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
285 if (!token_pos) {
286 av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
287 return;
288 }
289
290 length = token_pos - mmc->diagram_config->html_template;
291 for (uint64_t i = 0; i < length; i++)
292 writer_w8(tfc, mmc->diagram_config->html_template[i]);
293 }
294
295 writer_put_str(tfc, directive);
296 switch (mmc->diagram_config->diagram_type) {
297 case AV_DIAGRAMTYPE_GRAPH:
298 writer_put_str(tfc, "flowchart LR\n");
299 ////writer_put_str(tfc, " gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
300 writer_put_str(tfc, " gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%); \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%); \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%); \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
301 break;
302 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
303 writer_put_str(tfc, "erDiagram\n");
304 break;
305 }
306
307 av_bprint_finalize(&css_buf, NULL);
308 av_freep(&directive);
309 return;
310 }
311
312 if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
313 mermaid_subgraph_complete_start(mmc, tfc, tfc->level - 1);
314 }
315
316 av_freep(&mmc->section_data[tfc->level].section_id);
317 av_freep(&mmc->section_data[tfc->level].section_type);
318 av_freep(&mmc->section_data[tfc->level].src_id);
319 av_freep(&mmc->section_data[tfc->level].dest_id);
320 mmc->section_data[tfc->level].current_is_textblock = 0;
321 mmc->section_data[tfc->level].current_is_stadium = 0;
322 mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
323 mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
324
325 // NOTE: av_strdup() allocations aren't checked
326 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
327
328 av_bprint_clear(buf);
329 writer_put_str(tfc, "\n");
330
331 mmc->indent_level++;
332
333 if (sec_ctx->context_id) {
334 MM_INDENT();
335 writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
336 } else {
337 av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
338 }
339
340 mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
341 set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
342 }
343
344 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
345
346 av_bprint_clear(buf);
347 writer_put_str(tfc, "\n");
348
349 mmc->indent_level++;
350
351 if (sec_ctx->context_id) {
352
353 set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
354
355 switch (mmc->diagram_config->diagram_type) {
356 case AV_DIAGRAMTYPE_GRAPH:
357 if (sec_ctx->context_flags & 1) {
358
359 MM_INDENT();
360 writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
361 mmc->section_data[tfc->level].current_is_textblock = 1;
362 } else if (sec_ctx->context_flags & 2) {
363
364 MM_INDENT();
365 writer_printf(tfc, "%s([\"", sec_ctx->context_id);
366 mmc->section_data[tfc->level].current_is_stadium = 1;
367 } else {
368 MM_INDENT();
369 writer_printf(tfc, "%s(\"", sec_ctx->context_id);
370 }
371
372 break;
373 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
374 MM_INDENT();
375 writer_printf(tfc, "%s {\n", sec_ctx->context_id);
376 break;
377 }
378
379 } else {
380 av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
381 }
382
383 set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
384 }
385
386
387 if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
388
389 if (sec_ctx && sec_ctx->context_type)
390 writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
391 else
392 writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
393 }
394
395
396 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
397
398 av_bprint_clear(buf);
399 mmc->nb_link_captions[tfc->level] = 0;
400
401 if (sec_ctx && sec_ctx->context_type)
402 set_str(&mmc->section_data[tfc->level].section_type, sec_ctx->context_type);
403
404 ////if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
405 //// AVBPrint buf;
406 //// av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
407 //// av_bprint_escape(&buf, section->get_type(data), NULL,
408 //// AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
409 //// writer_printf(tfc, " type=\"%s\"", buf.str);
410 }
411 }
412
413 static void mermaid_print_section_footer(AVTextFormatContext *tfc)
414 {
415 MermaidContext *mmc = tfc->priv;
416 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
417
418 if (!section)
419 return;
420 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
421 struct section_data sec_data = mmc->section_data[tfc->level];
422
423 if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
424 writer_put_str(tfc, "</div>");
425
426 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
427
428 switch (mmc->diagram_config->diagram_type) {
429 case AV_DIAGRAMTYPE_GRAPH:
430
431 if (sec_data.current_is_textblock) {
432 writer_printf(tfc, "\"}\n", section->name);
433
434 if (sec_data.section_id) {
435 MM_INDENT();
436 writer_put_str(tfc, "class ");
437 writer_put_str(tfc, sec_data.section_id);
438 writer_put_str(tfc, " ff-");
439 writer_put_str(tfc, section->name);
440 writer_put_str(tfc, "\n");
441 }
442 } else if (sec_data.current_is_stadium) {
443 writer_printf(tfc, "\"]):::ff-%s\n", section->name);
444 } else {
445 writer_printf(tfc, "\"):::ff-%s\n", section->name);
446 }
447
448 break;
449 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
450 MM_INDENT();
451 writer_put_str(tfc, "}\n\n");
452 break;
453 }
454
455 mmc->indent_level--;
456
457 } else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
458
459 mermaid_subgraph_complete_start(mmc, tfc, tfc->level);
460
461 MM_INDENT();
462 writer_put_str(tfc, "end\n");
463
464 if (sec_data.section_id) {
465 MM_INDENT();
466 writer_put_str(tfc, "class ");
467 writer_put_str(tfc, sec_data.section_id);
468 writer_put_str(tfc, " ff-");
469 writer_put_str(tfc, section->name);
470 writer_put_str(tfc, "\n");
471 }
472
473 mmc->indent_level--;
474 }
475
476 if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
477 if (sec_data.src_id && sec_data.dest_id
478 && !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
479 switch (mmc->diagram_config->diagram_type) {
480 case AV_DIAGRAMTYPE_GRAPH:
481
482 if (sec_data.section_type && mmc->enable_link_colors)
483 av_bprintf(&mmc->link_buf, "\n %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
484 else
485 av_bprintf(&mmc->link_buf, "\n %s ==", sec_data.src_id);
486
487 if (buf->len > 0) {
488 av_bprintf(&mmc->link_buf, " \"%s", buf->str);
489
490 for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
491 av_bprintf(&mmc->link_buf, "<br>&nbsp;");
492
493 av_bprintf(&mmc->link_buf, "\" ==");
494 }
495
496 av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
497
498 break;
499 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
500
501
502 av_bprintf(&mmc->link_buf, "\n %s", sec_data.src_id);
503
504 switch (sec_data.link_type) {
505 case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
506 av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
507 break;
508 case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
509 av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
510 break;
511 case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
512 av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
513 break;
514 case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
515 av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
516 break;
517 default:
518 av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
519 break;
520 }
521
522 av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
523
524 break;
525 }
526
527 if (tfc->level == 0) {
528
529 writer_put_str(tfc, "\n");
530 if (mmc->create_html) {
531 char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
532 if (!token_pos) {
533 av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
534 return;
535 }
536 token_pos += strlen("__###__");
537 writer_put_str(tfc, token_pos);
538 }
539 }
540
541 if (tfc->level == 1) {
542
543 if (mmc->link_buf.len > 0) {
544 writer_put_str(tfc, mmc->link_buf.str);
545 av_bprint_clear(&mmc->link_buf);
546 }
547
548 writer_put_str(tfc, "\n");
549 }
550 }
551
552 static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
553 const char *str, int64_t num, const int is_int)
554 {
555 MermaidContext *mmc = tfc->priv;
556 const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
557
558 if (!section)
559 return;
560
561 AVBPrint *buf = &tfc->section_pbuf[tfc->level];
562 struct section_data sec_data = mmc->section_data[tfc->level];
563 int exit = 0;
564
565 if (section->id_key && !strcmp(section->id_key, key)) {
566 set_str(&mmc->section_data[tfc->level].section_id, str);
567 exit = 1;
568 }
569
570 if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
571 set_str(&mmc->section_data[tfc->level].dest_id, str);
572 exit = 1;
573 }
574
575 if (section->src_id_key && !strcmp(section->src_id_key, key)) {
576 set_str(&mmc->section_data[tfc->level].src_id, str);
577 exit = 1;
578 }
579
580 if (section->linktype_key && !strcmp(section->linktype_key, key)) {
581 mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;
582 exit = 1;
583 }
584
585 if (exit)
586 return;
587
588 if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
589 || (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
590 switch (mmc->diagram_config->diagram_type) {
591 case AV_DIAGRAMTYPE_GRAPH:
592
593 if (is_int) {
594 writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
595 } else {
596 const char *tmp = av_strireplace(str, "\"", "'");
597 writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
598 av_freep(&tmp);
599 }
600
601 break;
602 case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
603
604 if (!is_int && str)
605 {
606 const char *col_type;
607
608 if (key[0] == '_')
609 return;
610
611 if (sec_data.section_id && !strcmp(str, sec_data.section_id))
612 col_type = "PK";
613 else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
614 col_type = "FK";
615 else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
616 col_type = "FK";
617 else
618 col_type = "";
619
620 MM_INDENT();
621
622 writer_printf(tfc, " %s %s %s\n", key, str, col_type);
623 }
624 break;
625 }
626
627 } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
628 if (buf->len > 0)
629 av_bprintf(buf, "%s", "<br>");
630
631 av_bprintf(buf, "");
632 if (is_int)
633 av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
634 else
635 av_bprintf(buf, "<span>%s</span>", str);
636
637 mmc->nb_link_captions[tfc->level]++;
638 }
639 }
640
641 static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
642 {
643 mermaid_print_value(tfc, key, value, 0, 0);
644 }
645
646 static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
647 {
648 mermaid_print_value(tfc, key, NULL, value, 1);
649 }
650
651 const AVTextFormatter avtextformatter_mermaid = {
652 .name = "mermaid",
653 .priv_size = sizeof(MermaidContext),
654 .init = mermaid_init,
655 .uninit = mermaid_uninit,
656 .print_section_header = mermaid_print_section_header,
657 .print_section_footer = mermaid_print_section_footer,
658 .print_integer = mermaid_print_int,
659 .print_string = mermaid_print_str,
660 .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
661 .priv_class = &mermaid_class,
662 };
663
664
665 const AVTextFormatter avtextformatter_mermaidhtml = {
666 .name = "mermaidhtml",
667 .priv_size = sizeof(MermaidContext),
668 .init = mermaid_init_html,
669 .uninit = mermaid_uninit,
670 .print_section_header = mermaid_print_section_header,
671 .print_section_footer = mermaid_print_section_footer,
672 .print_integer = mermaid_print_int,
673 .print_string = mermaid_print_str,
674 .flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
675 .priv_class = &mermaid_class,
676 };
677