3333
3434#define MAX_USE_DEPTH 100
3535
36+ typedef struct svg_text_path svg_text_path ;
37+
3638typedef struct svg_state
3739{
3840 fz_matrix transform ;
@@ -58,8 +60,141 @@ typedef struct svg_state
5860 int is_bold ;
5961 int is_italic ;
6062 int text_anchor ;
63+ const svg_text_path * text_path ; /* SumatraPDF: inside <textPath> */
6164} svg_state ;
6265
66+ /* SumatraPDF: text on a path (#6332). The referenced path flattened to line
67+ * segments; glyphs are placed by their distance along it. */
68+ typedef struct
69+ {
70+ float x0 , y0 , x1 , y1 , len , start ;
71+ } svg_text_path_seg ;
72+
73+ struct svg_text_path
74+ {
75+ svg_text_path_seg * segs ;
76+ int n , cap ;
77+ float cur_x , cur_y , sub_x , sub_y , total ;
78+ };
79+
80+ static void
81+ svg_text_path_add (fz_context * ctx , svg_text_path * tp , float x , float y )
82+ {
83+ float dx = x - tp -> cur_x , dy = y - tp -> cur_y ;
84+ float len = sqrtf (dx * dx + dy * dy );
85+ if (len > 0 )
86+ {
87+ svg_text_path_seg * seg ;
88+ if (tp -> n == tp -> cap )
89+ {
90+ int cap = tp -> cap ? tp -> cap * 2 : 64 ;
91+ tp -> segs = fz_realloc_array (ctx , tp -> segs , cap , svg_text_path_seg );
92+ tp -> cap = cap ;
93+ }
94+ seg = & tp -> segs [tp -> n ++ ];
95+ seg -> x0 = tp -> cur_x ;
96+ seg -> y0 = tp -> cur_y ;
97+ seg -> x1 = x ;
98+ seg -> y1 = y ;
99+ seg -> len = len ;
100+ seg -> start = tp -> total ;
101+ tp -> total += len ;
102+ }
103+ tp -> cur_x = x ;
104+ tp -> cur_y = y ;
105+ }
106+
107+ static void
108+ svg_text_path_moveto (fz_context * ctx , void * arg , float x , float y )
109+ {
110+ svg_text_path * tp = arg ;
111+ tp -> cur_x = tp -> sub_x = x ;
112+ tp -> cur_y = tp -> sub_y = y ;
113+ }
114+
115+ static void
116+ svg_text_path_lineto (fz_context * ctx , void * arg , float x , float y )
117+ {
118+ svg_text_path_add (ctx , arg , x , y );
119+ }
120+
121+ static void
122+ svg_text_path_curveto (fz_context * ctx , void * arg , float x1 , float y1 , float x2 , float y2 , float x3 , float y3 )
123+ {
124+ svg_text_path * tp = arg ;
125+ float x0 = tp -> cur_x , y0 = tp -> cur_y ;
126+ int i , n = 16 ;
127+ for (i = 1 ; i <= n ; i ++ )
128+ {
129+ float t = (float )i / n , u = 1 - t ;
130+ float x = u * u * u * x0 + 3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t * x3 ;
131+ float y = u * u * u * y0 + 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t * y3 ;
132+ svg_text_path_add (ctx , tp , x , y );
133+ }
134+ }
135+
136+ static void
137+ svg_text_path_closepath (fz_context * ctx , void * arg )
138+ {
139+ svg_text_path * tp = arg ;
140+ svg_text_path_add (ctx , tp , tp -> sub_x , tp -> sub_y );
141+ }
142+
143+ static const fz_path_walker svg_text_path_walker = {
144+ svg_text_path_moveto ,
145+ svg_text_path_lineto ,
146+ svg_text_path_curveto ,
147+ svg_text_path_closepath ,
148+ };
149+
150+ /* point and unit tangent at distance d along the path; 0 when d is off it */
151+ static int
152+ svg_text_path_at (const svg_text_path * tp , float d , float * x , float * y , float * tx , float * ty )
153+ {
154+ int i ;
155+ if (tp -> n == 0 || d < 0 || d > tp -> total )
156+ return 0 ;
157+ for (i = 0 ; i < tp -> n ; i ++ )
158+ {
159+ const svg_text_path_seg * seg = & tp -> segs [i ];
160+ if (d <= seg -> start + seg -> len || i == tp -> n - 1 )
161+ {
162+ float t = (d - seg -> start ) / seg -> len ;
163+ * x = seg -> x0 + (seg -> x1 - seg -> x0 ) * t ;
164+ * y = seg -> y0 + (seg -> y1 - seg -> y0 ) * t ;
165+ * tx = (seg -> x1 - seg -> x0 ) / seg -> len ;
166+ * ty = (seg -> y1 - seg -> y0 ) / seg -> len ;
167+ return 1 ;
168+ }
169+ }
170+ return 0 ;
171+ }
172+
173+ /* Like fz_show_string, but trm.e is the distance along the path and each
174+ * glyph is rotated to the tangent at its midpoint. */
175+ static fz_matrix
176+ svg_show_string_on_path (fz_context * ctx , fz_text * text , fz_font * user_font , fz_matrix trm , const char * s , const svg_text_path * tp )
177+ {
178+ float fs = trm .a ;
179+ while (* s )
180+ {
181+ fz_font * font ;
182+ int ucs , gid ;
183+ float adv , w , x , y , tx , ty ;
184+ s += fz_chartorune (& ucs , s );
185+ gid = fz_encode_character_with_fallback (ctx , user_font , ucs , 0 , FZ_LANG_UNSET , & font );
186+ adv = gid >= 0 ? fz_advance_glyph (ctx , font , gid , 0 ) : 0 ;
187+ w = adv * fs ;
188+ if (svg_text_path_at (tp , trm .e + w / 2 , & x , & y , & tx , & ty ))
189+ {
190+ fz_matrix gm = { fs * tx , fs * ty , fs * ty , - fs * tx , x - tx * w / 2 , y - ty * w / 2 };
191+ fz_show_glyph_aux (ctx , text , font , gm , adv , gid , ucs , ucs , 0 , 0 , FZ_BIDI_LTR , FZ_LANG_UNSET );
192+ }
193+ trm .e += w ;
194+ }
195+ return trm ;
196+ }
197+
63198static void svg_parse_common (fz_context * ctx , svg_document * doc , fz_xml * node , svg_state * state );
64199static void svg_run_element (fz_context * ctx , fz_device * dev , svg_document * doc , fz_xml * root , const svg_state * state );
65200
@@ -1746,7 +1881,10 @@ svg_run_text_string(fz_context *ctx, fz_device *dev, fz_matrix trm, const char *
17461881 trm .e -= (adv .e - trm .e );
17471882 }
17481883
1749- trm = fz_show_string (ctx , text , font , trm , s , 0 , 0 , FZ_BIDI_LTR , FZ_LANG_UNSET );
1884+ if (state -> text_path )
1885+ trm = svg_show_string_on_path (ctx , text , font , trm , s , state -> text_path );
1886+ else
1887+ trm = fz_show_string (ctx , text , font , trm , s , 0 , 0 , FZ_BIDI_LTR , FZ_LANG_UNSET );
17501888
17511889 if (state -> fill_is_set )
17521890 fz_fill_text (ctx , dev , text ,
@@ -1813,6 +1951,10 @@ svg_run_text(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, c
18131951 char * y_att = fz_xml_att (root , "y" );
18141952 char * dx_att = fz_xml_att (root , "dx" );
18151953 char * dy_att = fz_xml_att (root , "dy" );
1954+ svg_text_path tp = { 0 };
1955+ fz_path * path = NULL ;
1956+
1957+ fz_var (path );
18161958
18171959 svg_begin_state (ctx , & local_state , inherit_state );
18181960 fz_try (ctx )
@@ -1824,6 +1966,26 @@ svg_run_text(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, c
18241966 trm .e = x ;
18251967 trm .f = y ;
18261968
1969+ /* SumatraPDF: text on a path (#6332) */
1970+ if (fz_xml_is_tag (root , "textPath" ))
1971+ {
1972+ char * href_att = fz_xml_att_alt (root , "xlink:href" , "href" );
1973+ char * offset_att = fz_xml_att (root , "startOffset" );
1974+ fz_xml * linked = (href_att && href_att [0 ] == '#' ) ? fz_tree_lookup (ctx , doc -> idmap , href_att + 1 ) : NULL ;
1975+ char * d_att = linked ? fz_xml_att (linked , "d" ) : NULL ;
1976+ if (d_att )
1977+ {
1978+ path = fz_new_path (ctx );
1979+ svg_parse_path_data (ctx , path , d_att );
1980+ fz_walk_path (ctx , path , & svg_text_path_walker , & tp );
1981+ fz_drop_path (ctx , path );
1982+ path = NULL ;
1983+ local_state .text_path = & tp ;
1984+ trm .e = offset_att ? svg_parse_length (offset_att , tp .total , local_state .fontsize ) : 0 ;
1985+ trm .f = 0 ;
1986+ }
1987+ }
1988+
18271989 if (x_att ) trm .e = svg_parse_length (x_att , local_state .viewbox_w , local_state .fontsize );
18281990 if (y_att ) trm .f = svg_parse_length (y_att , local_state .viewbox_h , local_state .fontsize );
18291991
@@ -1848,7 +2010,11 @@ svg_run_text(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, c
18482010 }
18492011 }
18502012 fz_always (ctx )
2013+ {
2014+ fz_drop_path (ctx , path );
2015+ fz_free (ctx , tp .segs );
18512016 svg_end_state (ctx , & local_state );
2017+ }
18522018 fz_catch (ctx )
18532019 fz_rethrow (ctx );
18542020
@@ -2001,6 +2167,7 @@ svg_run_document(fz_context *ctx, svg_document *doc, fz_xml *root, fz_device *de
20012167 state .is_bold = 0 ;
20022168 state .is_italic = 0 ;
20032169 state .text_anchor = 0 ;
2170+ state .text_path = NULL ;
20042171
20052172 fz_try (ctx )
20062173 {
0 commit comments