@@ -151,26 +151,58 @@ export function parseDeckCssVariables(cssOrHtml: string): ViewportSize | null {
151151}
152152
153153/**
154- * Read fixed `width`/`height` px on common slide host / root selectors.
154+ * Extract text content of `<style>` blocks from HTML.
155+ * Used so CSS rule scanners never run against markup / data-URI blobs
156+ * (those can be multi-MB lines without `{`/`}`, which makes `/[^{}@]+\{/` O(n²)).
155157 */
156- export function parseFixedSlideHostSize ( cssOrHtml : string ) : ViewportSize | null {
157- // Match CSS rule blocks; keep it simple (no nested @rules required for our cases).
158- const ruleRe = / ( [ ^ { } @ ] + ) \{ ( [ ^ { } ] + ) \} / g ;
158+ export function extractInlineStyleBlocks ( html : string ) : string {
159+ const chunks : string [ ] = [ ] ;
160+ const styleRe = / < s t y l e \b [ ^ > ] * > ( [ \s \S ] * ? ) < \/ s t y l e > / gi ;
159161 let match : RegExpExecArray | null ;
160- while ( ( match = ruleRe . exec ( cssOrHtml ) ) !== null ) {
161- const selectors = match [ 1 ] ! ;
162- const body = match [ 2 ] ! ;
163- if ( ! SLIDE_HOST_SELECTOR_RE . test ( selectors ) ) continue ;
162+ while ( ( match = styleRe . exec ( html ) ) !== null ) {
163+ const css = match [ 1 ] ;
164+ if ( css ) chunks . push ( css ) ;
165+ }
166+ return chunks . join ( '\n' ) ;
167+ }
164168
165- const widthMatch = body . match ( / (?: ^ | [ ; \s ] ) w i d t h \s * : \s * ( [ ^ ; } \n ] + ) / i) ;
166- const heightMatch = body . match ( / (?: ^ | [ ; \s ] ) h e i g h t \s * : \s * ( [ ^ ; } \n ] + ) / i) ;
167- if ( ! widthMatch || ! heightMatch ) continue ;
168- const width = parseCssPxLength ( widthMatch [ 1 ] ! ) ;
169- const height = parseCssPxLength ( heightMatch [ 1 ] ! ) ;
170- if ( width == null || height == null ) continue ;
171- // Ignore tiny decorative boxes accidentally matching a host selector.
172- if ( width < 320 || height < 180 ) continue ;
173- return { width, height } ;
169+ /**
170+ * CSS chunks safe to scan with the CSS-rule regex.
171+ * Prefer `<style>` contents when present; skip HTML markup (no style tags);
172+ * otherwise treat the whole string as CSS (linked stylesheets / pure CSS).
173+ */
174+ function cssChunksForRuleScan ( cssOrHtml : string ) : string [ ] {
175+ const inline = extractInlineStyleBlocks ( cssOrHtml ) ;
176+ if ( inline ) return [ inline ] ;
177+ // HTML without <style> — do not scan body / base64 blobs.
178+ if ( / < [ a - z A - Z ! / ? ] / . test ( cssOrHtml ) ) return [ ] ;
179+ return [ cssOrHtml ] ;
180+ }
181+
182+ /**
183+ * Read fixed `width`/`height` px on common slide host / root selectors.
184+ * Scans only CSS (inline `<style>` blocks or pure stylesheet text), never raw HTML.
185+ */
186+ export function parseFixedSlideHostSize ( cssOrHtml : string ) : ViewportSize | null {
187+ for ( const css of cssChunksForRuleScan ( cssOrHtml ) ) {
188+ // Match CSS rule blocks; keep it simple (no nested @rules required for our cases).
189+ const ruleRe = / ( [ ^ { } @ ] + ) \{ ( [ ^ { } ] + ) \} / g;
190+ let match : RegExpExecArray | null ;
191+ while ( ( match = ruleRe . exec ( css ) ) !== null ) {
192+ const selectors = match [ 1 ] ! ;
193+ const body = match [ 2 ] ! ;
194+ if ( ! SLIDE_HOST_SELECTOR_RE . test ( selectors ) ) continue ;
195+
196+ const widthMatch = body . match ( / (?: ^ | [ ; \s ] ) w i d t h \s * : \s * ( [ ^ ; } \n ] + ) / i) ;
197+ const heightMatch = body . match ( / (?: ^ | [ ; \s ] ) h e i g h t \s * : \s * ( [ ^ ; } \n ] + ) / i) ;
198+ if ( ! widthMatch || ! heightMatch ) continue ;
199+ const width = parseCssPxLength ( widthMatch [ 1 ] ! ) ;
200+ const height = parseCssPxLength ( heightMatch [ 1 ] ! ) ;
201+ if ( width == null || height == null ) continue ;
202+ // Ignore tiny decorative boxes accidentally matching a host selector.
203+ if ( width < 320 || height < 180 ) continue ;
204+ return { width, height } ;
205+ }
174206 }
175207 return null ;
176208}
@@ -241,17 +273,19 @@ function detectFromCssSources(...sources: string[]): ViewportSize | null {
241273 * Detect declared slide size from HTML source.
242274 * Order: deck-size meta → CSS size vars → viewport meta → stage JSON → fixed host px.
243275 * When `baseDir` is set, also reads local linked stylesheets for CSS signals.
276+ * CSS detectors only see `<style>` contents + linked stylesheets (not HTML body / data URIs).
244277 */
245278export function detectViewportFromHtml (
246279 html : string ,
247280 options : { baseDir ?: string } = { }
248281) : ViewportSize | null {
249282 const linkedCss =
250283 options . baseDir != null ? readLocalStylesheets ( html , options . baseDir ) : '' ;
284+ const inlineCss = extractInlineStyleBlocks ( html ) ;
251285
252286 return (
253287 parseDeckSizeMeta ( html ) ??
254- detectFromCssSources ( html , linkedCss ) ??
288+ detectFromCssSources ( inlineCss , linkedCss ) ??
255289 parseViewportMeta ( html ) ??
256290 parseStageJson ( html )
257291 ) ;
0 commit comments