| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Cyril Concolato, Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Scene Management sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/utf.h> | |
| #include <gpac/xml.h> | |
| #include <gpac/internal/swf_dev.h> | |
| #include <gpac/internal/scenegraph_dev.h> | |
| #include <gpac/internal/isomedia_dev.h> | |
| #include <gpac/internal/media_dev.h> | |
| #include <gpac/constants.h> | |
| #ifndef GPAC_DISABLE_VVG | |
| #ifndef GPAC_DISABLE_SWF_IMPORT | |
| #define SWF_TEXT_SCALE (1/1024.0f) | |
| typedef struct _swf_svg_sample | |
| { | |
| u64 start; | |
| u64 end; | |
| char *data; | |
| } GF_SWF_SVG_Sample; | |
| static void swf_svg_print(SWFReader *read, const char *format, ...) { | |
| char line[2000]; | |
| u32 line_length; | |
| u32 new_size; | |
| va_list args; | |
| /* print the line */ | |
| va_start(args, format); | |
| vsnprintf(line, 2000, format, args); | |
| va_end(args); | |
| /* add the line to the buffer */ | |
| line_length = (u32)strlen(line); | |
| new_size = read->svg_data_size+line_length; | |
| read->svg_data = (char *)gf_realloc(read->svg_data, new_size+1); | |
| if (read->print_frame_header) { | |
| /* write at the beginning of the buffer */ | |
| memmove(read->svg_data+read->frame_header_offset+line_length, read->svg_data+read->frame_header_offset, (read->svg_data_size-read->frame_header_offset)+1); | |
| memcpy(read->svg_data+read->frame_header_offset, line, line_length); | |
| read->frame_header_offset += line_length; | |
| } else { | |
| memcpy(read->svg_data+read->svg_data_size, line, line_length+1); | |
| } | |
| read->svg_data_size = new_size; | |
| } | |
| static void swf_svg_print_color(SWFReader *read, u32 ARGB) | |
| { | |
| SFColor val; | |
| val.red = INT2FIX((ARGB>>16)&0xFF) / 255*100; | |
| val.green = INT2FIX((ARGB>>8)&0xFF) / 255*100; | |
| val.blue = INT2FIX((ARGB)&0xFF) / 255*100; | |
| swf_svg_print(read, "rgb(%g%%,%g%%,%g%%)", FIX2FLT(val.red), FIX2FLT(val.green), FIX2FLT(val.blue)); | |
| } | |
| static void swf_svg_print_alpha(SWFReader *read, u32 ARGB) | |
| { | |
| Fixed alpha; | |
| alpha = INT2FIX((ARGB>>24)&0xFF)/255; | |
| swf_svg_print(read, "%g", FIX2FLT(alpha)); | |
| } | |
| static void swg_svg_print_shape_record_to_fill_stroke(SWFReader *read, SWFShapeRec *srec, Bool is_fill) | |
| { | |
| /*get regular appearance reuse*/ | |
| if (is_fill) { | |
| switch (srec->type) { | |
| /*solid/alpha fill*/ | |
| case 0x00: | |
| swf_svg_print(read, "fill=\""); | |
| swf_svg_print_color(read, srec->solid_col); | |
| swf_svg_print(read, "\" "); | |
| swf_svg_print(read, "fill-opacity=\""); | |
| swf_svg_print_alpha(read, srec->solid_col); | |
| swf_svg_print(read, "\" "); | |
| break; | |
| case 0x10: | |
| case 0x12: | |
| //if (read->flags & GF_SM_SWF_NO_GRADIENT) { | |
| // u32 col = srec->grad_col[srec->nbGrad/2]; | |
| // col |= 0xFF000000; | |
| // n->appearance = s2b_get_appearance(read, (GF_Node *) n, col, 0, 0); | |
| //} else { | |
| // n->appearance = s2b_get_gradient(read, (GF_Node *) n, shape, srec); | |
| //} | |
| //break; | |
| case 0x40: | |
| case 0x41: | |
| case 0x42: | |
| case 0x43: | |
| //n->appearance = s2b_get_bitmap(read, (GF_Node *) n, shape, srec); | |
| //break; | |
| default: | |
| swf_report(read, GF_NOT_SUPPORTED, "fill_style %x not supported", srec->type); | |
| break; | |
| } | |
| } else { | |
| swf_svg_print(read, "fill=\"none\" "); | |
| swf_svg_print(read, "stroke=\""); | |
| swf_svg_print_color(read, srec->solid_col); | |
| swf_svg_print(read, "\" "); | |
| swf_svg_print(read, "stroke-opacity=\""); | |
| swf_svg_print_alpha(read, srec->solid_col); | |
| swf_svg_print(read, "\" "); | |
| swf_svg_print(read, "stroke-width=\"%g\" ", FIX2FLT(srec->width)); | |
| } | |
| } | |
| static void swf_svg_print_shape_record_to_path_d(SWFReader *read, SWFShapeRec *srec) | |
| { | |
| u32 pt_idx; | |
| u32 i; | |
| pt_idx = 0; | |
| for (i=0; i<srec->path->nbType; i++) { | |
| switch (srec->path->types[i]) { | |
| /*moveTo*/ | |
| case 0: | |
| swf_svg_print(read, "M%g,%g", FIX2FLT(srec->path->pts[pt_idx].x), FIX2FLT(srec->path->pts[pt_idx].y)); | |
| pt_idx++; | |
| break; | |
| /*lineTo*/ | |
| case 1: | |
| swf_svg_print(read, "L%g,%g", FIX2FLT(srec->path->pts[pt_idx].x), FIX2FLT(srec->path->pts[pt_idx].y)); | |
| pt_idx++; | |
| break; | |
| /*curveTo*/ | |
| case 2: | |
| swf_svg_print(read, "Q%g,%g", FIX2FLT(srec->path->pts[pt_idx].x), FIX2FLT(srec->path->pts[pt_idx].y)); | |
| pt_idx++; | |
| swf_svg_print(read, ",%g,%g", FIX2FLT(srec->path->pts[pt_idx].x), FIX2FLT(srec->path->pts[pt_idx].y)); | |
| pt_idx++; | |
| break; | |
| } | |
| } | |
| } | |
| static void swf_svg_print_matrix(SWFReader *read, GF_Matrix2D *mat) | |
| { | |
| if (!gf_mx2d_is_identity(*mat)) | |
| { | |
| GF_Point2D scale; | |
| GF_Point2D translate; | |
| Fixed rotate; | |
| if( gf_mx2d_decompose(mat, &scale, &rotate, &translate)) | |
| { | |
| swf_svg_print(read, "transform=\""); | |
| if (translate.x != 0 || translate.y != 0) | |
| { | |
| swf_svg_print(read, "translate(%g, %g) ", FIX2FLT(translate.x), FIX2FLT(translate.y)); | |
| } | |
| if (rotate != 0) | |
| { | |
| swf_svg_print(read, "rotate(%g) ", FIX2FLT(rotate)); | |
| } | |
| if (scale.x != FIX_ONE || scale.y != FIX_ONE) | |
| { | |
| swf_svg_print(read, "scale(%g, %g) ", FIX2FLT(scale.x), FIX2FLT(scale.y)); | |
| } | |
| swf_svg_print(read, "\" "); | |
| } | |
| else | |
| { | |
| swf_svg_print(read, "transform=\"matrix(%g,%g,%g,%g,%g,%g)\" ", FIX2FLT(mat->m[0]), FIX2FLT(mat->m[3]), FIX2FLT(mat->m[1]), FIX2FLT(mat->m[4]), FIX2FLT(mat->m[2]), FIX2FLT(mat->m[5]) ); | |
| } | |
| } | |
| } | |
| /*translates Flash to SVG shapes*/ | |
| static GF_Err swf_svg_define_shape(SWFReader *read, SWFShape *shape, SWFFont *parent_font, Bool last_sub_shape) | |
| { | |
| u32 i; | |
| SWFShapeRec *srec; | |
| if (parent_font && (read->flags & GF_SM_SWF_NO_FONT)) | |
| { | |
| return GF_OK; | |
| } | |
| if (!read->svg_shape_started) | |
| { | |
| swf_svg_print(read, "<defs>\n"); | |
| if (!parent_font) | |
| { | |
| swf_svg_print(read, "<g id=\"S%d\" >\n", shape->ID); | |
| } | |
| else | |
| { | |
| char szGlyphId[256]; | |
| sprintf(szGlyphId, "Font%d_Glyph%d", parent_font->fontID, gf_list_count(parent_font->glyphs)); | |
| swf_svg_print(read, "<g id=\"%s\" >\n", szGlyphId); | |
| gf_list_add(parent_font->glyphs, szGlyphId); | |
| } | |
| } | |
| read->empty_frame = GF_FALSE; | |
| read->svg_shape_started = GF_TRUE; | |
| i=0; | |
| while ((srec = (SWFShapeRec*)gf_list_enum(shape->fill_left, &i))) { | |
| swf_svg_print(read, "<path d=\""); | |
| swf_svg_print_shape_record_to_path_d(read, srec); | |
| swf_svg_print(read, "\" "); | |
| swg_svg_print_shape_record_to_fill_stroke(read, srec, 1); | |
| swf_svg_print(read, "/>\n"); | |
| } | |
| i=0; | |
| while ((srec = (SWFShapeRec*)gf_list_enum(shape->lines, &i))) { | |
| swf_svg_print(read, "<path d=\""); | |
| swf_svg_print_shape_record_to_path_d(read, srec); | |
| swf_svg_print(read, "\" "); | |
| swg_svg_print_shape_record_to_fill_stroke(read, srec, 0); | |
| swf_svg_print(read, "/>\n"); | |
| } | |
| if (last_sub_shape) | |
| { | |
| read->svg_shape_started = GF_FALSE; | |
| swf_svg_print(read, "</g>\n"); | |
| swf_svg_print(read, "</defs>\n"); | |
| } | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_define_text(SWFReader *read, SWFText *text) | |
| { | |
| Bool use_text; | |
| u32 i; | |
| u32 j; | |
| SWFGlyphRec *gr; | |
| SWFFont *ft; | |
| use_text = (read->flags & GF_SM_SWF_NO_FONT) ? 1 : 0; | |
| swf_svg_print(read, "<defs>\n"); | |
| swf_svg_print(read, "<g id=\"S%d\" ", text->ID); | |
| swf_svg_print_matrix(read, &text->mat); | |
| swf_svg_print(read, ">\n"); | |
| i=0; | |
| while ((gr = (SWFGlyphRec*)gf_list_enum(text->text, &i))) | |
| { | |
| ft = NULL; | |
| if (use_text) { | |
| ft = swf_find_font(read, gr->fontID); | |
| if (!ft->glyph_codes) { | |
| use_text = 0; | |
| swf_report(read, GF_BAD_PARAM, "Font glyphs are not defined, cannot reference extern font - Forcing glyph embedding"); | |
| } | |
| } | |
| if (use_text) { | |
| /*restore back the font height in pixels (it's currently in SWF glyph design units)*/ | |
| swf_svg_print(read, "<text "); | |
| swf_svg_print(read, "x=\"%g \" ", FIX2FLT(gr->orig_x)); | |
| swf_svg_print(read, "y=\"%g \" ", FIX2FLT(gr->orig_y)); | |
| swf_svg_print(read, "font-size=\"%d\" ", (u32)(gr->fontSize * SWF_TWIP_SCALE)); | |
| if (ft->fontName) | |
| { | |
| swf_svg_print(read, "font-family=\"%s\" ", ft->fontName); | |
| } | |
| if (ft->is_italic) | |
| { | |
| swf_svg_print(read, "font-style=\"italic\" "); | |
| } | |
| if (ft->is_bold) | |
| { | |
| swf_svg_print(read, "font-weight=\"bold\" "); | |
| } | |
| swf_svg_print(read, ">"); | |
| /*convert to UTF-8*/ | |
| { | |
| u32 _len; | |
| u16 *str_w; | |
| u16 *widestr; | |
| char *str; | |
| str_w = (u16*)gf_malloc(sizeof(u16) * (gr->nbGlyphs+1)); | |
| for (j=0; j<gr->nbGlyphs; j++) | |
| { | |
| str_w[j] = ft->glyph_codes[gr->indexes[j]]; | |
| } | |
| str_w[j] = 0; | |
| str = (char*)gf_malloc(sizeof(char) * (gr->nbGlyphs+2)); | |
| widestr = str_w; | |
| _len = gf_utf8_wcstombs(str, sizeof(u8) * (gr->nbGlyphs+1), (const unsigned short **) &widestr); | |
| if (_len != GF_UTF8_FAIL) { | |
| str[_len] = 0; | |
| swf_svg_print(read, "%s", str); | |
| } | |
| } | |
| swf_svg_print(read, "</text>\n"); | |
| } | |
| else | |
| { | |
| /*convert glyphs*/ | |
| Fixed dx; | |
| swf_svg_print(read, "<g tranform=\"scale(1,-1) "); | |
| swf_svg_print(read, "translate(%g, %g)\" >\n", FIX2FLT(gr->orig_x), FIX2FLT(gr->orig_y)); | |
| dx = 0; | |
| for (j=0; j<gr->nbGlyphs; j++) | |
| { | |
| swf_svg_print(read, "<use xlink:href=\"#Font%d_Glyph%d\" transform=\"translate(%g)\" />\n", gr->fontID, gr->indexes[j], FIX2FLT(gf_divfix(dx, FLT2FIX(gr->fontSize * SWF_TEXT_SCALE)))); | |
| dx += gr->dx[j]; | |
| } | |
| swf_svg_print(read, "</g>\n"); | |
| } | |
| } | |
| read->empty_frame = GF_FALSE; | |
| swf_svg_print(read, "</g>\n"); | |
| swf_svg_print(read, "</defs>\n"); | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_define_edit_text(SWFReader *read, SWFEditText *text) | |
| { | |
| return GF_OK; | |
| } | |
| #if 0 | |
| /*called upon end of sprite or clip*/ | |
| static void swf_svg_end_of_clip(SWFReader *read) | |
| { | |
| } | |
| #endif | |
| static Bool swf_svg_allocate_depth(SWFReader *read, u32 depth) | |
| { | |
| return GF_FALSE; | |
| } | |
| static GF_Err swf_svg_define_sprite(SWFReader *read, u32 nb_frames) | |
| { | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_setup_sound(SWFReader *read, SWFSound *snd, Bool soundstream_first_block) | |
| { | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_setup_image(SWFReader *read, u32 ID, char *fileName) | |
| { | |
| swf_svg_print(read, "<defs>\n"); | |
| swf_svg_print(read, "<image id=\"S%d\" xlink:href=\"\"/>", ID, fileName); | |
| swf_svg_print(read, "</defs>\n"); | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_set_backcol(SWFReader *read, u32 xrgb) | |
| { | |
| //rgb.red = INT2FIX((xrgb>>16) & 0xFF) / 255; | |
| //rgb.green = INT2FIX((xrgb>>8) & 0xFF) / 255; | |
| //rgb.blue = INT2FIX((xrgb) & 0xFF) / 255; | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_start_sound(SWFReader *read, SWFSound *snd, Bool stop) | |
| { | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_place_obj(SWFReader *read, u32 depth, u32 ID, u32 prev_id, u32 type, GF_Matrix2D *mat, GF_ColorMatrix *cmat, GF_Matrix2D *prev_mat, GF_ColorMatrix *prev_cmat) | |
| { | |
| read->empty_frame = GF_FALSE; | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_remove_obj(SWFReader *read, u32 depth, u32 ID) | |
| { | |
| read->empty_frame = GF_FALSE; | |
| return GF_OK; | |
| } | |
| static GF_Err swf_svg_show_frame(SWFReader *read) | |
| { | |
| u32 i; | |
| u32 len; | |
| GF_List *sdl = gf_list_new(); // sorted display list | |
| /* sorting the display list because SVG/CSS z-index is not well supported */ | |
| while (gf_list_count(read->display_list)) | |
| { | |
| Bool inserted = GF_FALSE; | |
| DispShape *s; | |
| s = (DispShape *)gf_list_get(read->display_list, 0); | |
| gf_list_rem(read->display_list, 0); | |
| for (i = 0; i < gf_list_count(sdl); i++) | |
| { | |
| DispShape *s2 = (DispShape *)gf_list_get(sdl, i); | |
| if (s->depth < s2->depth) | |
| { | |
| gf_list_insert(sdl, s, i); | |
| inserted = GF_TRUE; | |
| break; | |
| } | |
| } | |
| if (!inserted) | |
| { | |
| gf_list_add(sdl, s); | |
| } | |
| } | |
| gf_list_del(read->display_list); | |
| read->display_list = sdl; | |
| /* dumping the display list */ | |
| len = gf_list_count(read->display_list); | |
| for (i=0; i<len; i++) | |
| { | |
| DispShape *s; | |
| s = (DispShape *)gf_list_get(read->display_list, i); | |
| swf_svg_print(read, "<use xlink:href=\"#S%d\" ", s->char_id); | |
| //swf_svg_print(read, "z-index=\"%d\" ", s->depth); | |
| swf_svg_print_matrix(read, &s->mat); | |
| swf_svg_print(read, "/>\n"); | |
| read->empty_frame = GF_FALSE; | |
| } | |
| if (!read->empty_frame) { | |
| read->print_frame_header = GF_TRUE; | |
| read->frame_header_offset = 0; | |
| swf_svg_print(read, "<g display=\"none\">\n"); | |
| swf_svg_print(read, "<animate id=\"frame%d_anim\" attributeName=\"display\" to=\"inline\" ", read->current_frame); | |
| swf_svg_print(read, "begin=\"%g\" ", 1.0*(read->current_frame)/read->frame_rate); | |
| if (read->current_frame+1 < read->frame_count) { | |
| swf_svg_print(read, "end=\"frame%d_anim.begin\" fill=\"remove\" ", (read->current_frame+1)); | |
| } else { | |
| swf_svg_print(read, "fill=\"freeze\" "); | |
| } | |
| swf_svg_print(read, "/>\n"); | |
| read->print_frame_header = GF_FALSE; | |
| swf_svg_print(read, "</g>\n"); | |
| } | |
| if (read->svg_data && read->svg_data_size) | |
| read->add_sample(read->user, read->svg_data, read->svg_data_size, read->current_frame*1000/read->frame_rate, (read->current_frame == 0)); | |
| if (read->svg_data) { | |
| gf_free(read->svg_data); | |
| read->svg_data = NULL; | |
| } | |
| read->svg_data_size = 0; | |
| read->empty_frame = GF_TRUE; | |
| return GF_OK; | |
| } | |
| static void swf_svg_finalize(SWFReader *read, Bool is_destroy) | |
| { | |
| if (!is_destroy) { | |
| swf_svg_print(read, "</svg>\n"); | |
| read->add_header(read->user, read->svg_data, read->svg_data_size, GF_FALSE); | |
| } | |
| gf_free(read->svg_data); | |
| read->svg_data = NULL; | |
| read->svg_data_size = 0; | |
| } | |
| static GF_Err swf_svg_define_button(SWFReader *read, SWF_Button *btn) | |
| { | |
| return GF_OK; | |
| } | |
| static Bool swf_svg_action(SWFReader *read, SWFAction *act) | |
| { | |
| return GF_TRUE; | |
| } | |
| GF_Err swf_to_svg_init(SWFReader *read, u32 swf_flags, Float swf_flatten_angle) | |
| { | |
| if (!read->user) return GF_BAD_PARAM; | |
| /*init callbacks*/ | |
| read->show_frame = swf_svg_show_frame; | |
| read->allocate_depth = swf_svg_allocate_depth; | |
| read->place_obj = swf_svg_place_obj; | |
| read->remove_obj = swf_svg_remove_obj; | |
| read->define_shape = swf_svg_define_shape; | |
| read->define_sprite = swf_svg_define_sprite; | |
| read->set_backcol = swf_svg_set_backcol; | |
| read->define_button = swf_svg_define_button; | |
| read->define_text = swf_svg_define_text; | |
| read->define_edit_text = swf_svg_define_edit_text; | |
| read->setup_sound = swf_svg_setup_sound; | |
| read->start_sound = swf_svg_start_sound; | |
| read->setup_image = swf_svg_setup_image; | |
| read->action = swf_svg_action; | |
| read->finalize = swf_svg_finalize; | |
| read->flags = swf_flags; | |
| read->flat_limit = FLT2FIX(swf_flatten_angle); | |
| read->print_stream_header = GF_TRUE; | |
| swf_svg_print(read, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | |
| swf_svg_print(read, "<svg xmlns=\"http://www.w3.org/2000/svg\" "); | |
| swf_svg_print(read, "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "); | |
| swf_svg_print(read, "width=\"100%%\" "); | |
| swf_svg_print(read, "height=\"100%%\" "); | |
| swf_svg_print(read, "viewBox=\"0 0 %d %d\" ", FIX2INT(read->width), FIX2INT(read->height)); | |
| swf_svg_print(read, "viewport-fill=\"rgb(255,255,255)\" "); | |
| swf_svg_print(read, ">\n"); | |
| read->print_stream_header = GF_FALSE; | |
| /* update sample description */ | |
| read->add_header(read->user, read->svg_data, read->svg_data_size, GF_TRUE); | |
| gf_free(read->svg_data); | |
| read->svg_data = NULL; | |
| read->svg_data_size = 0; | |
| return GF_OK; | |
| } | |
| GF_Err swf_svg_write_text_sample(void *user, const u8 *data, u32 length, u64 timestamp, Bool isRap) | |
| { | |
| FILE *svgFile = (FILE *)user; | |
| u32 lengthWritten; | |
| lengthWritten = (u32) gf_fwrite(data, length, svgFile); | |
| if (length != lengthWritten) { | |
| return GF_IO_ERR; | |
| } else { | |
| return GF_OK; | |
| } | |
| } | |
| GF_Err swf_svg_write_text_header(void *user, const u8 *data, u32 length, Bool isHeader) | |
| { | |
| FILE *svgFile = (FILE *)user; | |
| u32 lengthWritten; | |
| lengthWritten = (u32) gf_fwrite(data, length, svgFile); | |
| if (length != lengthWritten) { | |
| return GF_IO_ERR; | |
| } else { | |
| return GF_OK; | |
| } | |
| } | |
| #endif /*GPAC_DISABLE_SWF_IMPORT*/ | |
| #endif /*GPAC_DISABLE_SVG*/ |