├── Podspecs ├── LICENSE ├── Sources ├── CGGenBytecode.podspec ├── CGGenBytecodeDecoding.podspec ├── CGGenRTSupport.podspec └── README.md ├── .periphery.yml ├── Demo ├── cggendemo.shortcut ├── Demo │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Images │ │ ├── star.svg │ │ ├── heart.svg │ │ ├── gear.svg │ │ ├── mountain.svg │ │ └── rocket.svg │ ├── Demo.entitlements │ ├── DemoApp.swift │ └── SwiftUIDemo.swift ├── Demo.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── Demo.xcscheme └── CLAUDE.md ├── Tests ├── RegressionTests │ ├── tests.sketch │ ├── pdf_samples │ │ ├── fill.pdf │ │ ├── alpha.pdf │ │ ├── dashes.pdf │ │ ├── lines.pdf │ │ ├── shapes.pdf │ │ ├── gradient.pdf │ │ ├── caps_joins.pdf │ │ ├── gradient_shape.pdf │ │ ├── group_opacity.pdf │ │ ├── gradient_radial.pdf │ │ ├── gradient_three_dots.pdf │ │ ├── gradient_with_alpha.pdf │ │ ├── gradient_with_mask.pdf │ │ ├── nested_transparent_group.pdf │ │ ├── underlying_object_with_tiny_alpha.pdf │ │ ├── extgstate_multiple_params.pdf │ │ └── white_cross_scn_operator.pdf │ ├── __Snapshots__ │ │ ├── SVGTests │ │ │ ├── webkit-references.alpha.png │ │ │ ├── webkit-references.fill.png │ │ │ ├── webkit-references.lines.png │ │ │ ├── webkit-references.dashes.png │ │ │ ├── webkit-references.gradient.png │ │ │ ├── webkit-references.shapes.png │ │ │ ├── webkit-references.use_tag.png │ │ │ ├── webkit-references.caps_joins.png │ │ │ ├── webkit-references.clip_path.png │ │ │ ├── webkit-references.colornames.png │ │ │ ├── webkit-references.transforms.png │ │ │ ├── webkit-references.group_opacity.png │ │ │ ├── webkit-references.miter_limit.png │ │ │ ├── webkit-references.shadow_colors.png │ │ │ ├── webkit-references.shadow_simple.png │ │ │ ├── webkit-references.simple_mask.png │ │ │ ├── webkit-references.gradient_radial.png │ │ │ ├── webkit-references.gradient_shape.png │ │ │ ├── webkit-references.gradient_stroke.png │ │ │ ├── webkit-references.gradient_units.png │ │ │ ├── webkit-references.path_fill_rule.png │ │ │ ├── webkit-references.gradient_opacity.png │ │ │ ├── webkit-references.gradient_relative.png │ │ │ ├── webkit-references.gradient_with_mask.png │ │ │ ├── webkit-references.lines_and_curves.png │ │ │ ├── webkit-references.path_complex_curve.png │ │ │ ├── webkit-references.path_smooth_curve.png │ │ │ ├── webkit-references.paths_and_images.png │ │ │ ├── webkit-references.shadow_blur_radius.png │ │ │ ├── webkit-references.gradient_three_dots.png │ │ │ ├── webkit-references.gradient_with_alpha.png │ │ │ ├── webkit-references.path_circle_commands.png │ │ │ ├── webkit-references.path_short_commands.png │ │ │ ├── webkit-references.path_fill_rule_gstate.png │ │ │ ├── webkit-references.path_move_to_commands.png │ │ │ ├── webkit-references.path_quadratic_bezier.png │ │ │ ├── webkit-references.path_relative_commands.png │ │ │ ├── webkit-references.gradient_determinism_test.png │ │ │ ├── webkit-references.gradient_transform_linear.png │ │ │ ├── webkit-references.gradient_transform_radial.png │ │ │ ├── webkit-references.nested_transparent_group.png │ │ │ ├── webkit-references.white_cross_scn_operator.png │ │ │ ├── webkit-references.gradient_absolute_start_end.png │ │ │ ├── webkit-references.use_referencing_not_in_defs.png │ │ │ ├── webkit-references.path_fill_rule_nonzero_default.png │ │ │ ├── webkit-references.gradient_fill_stroke_combinations.png │ │ │ ├── webkit-references.topmost_presentation_attributes.png │ │ │ └── webkit-references.underlying_object_with_tiny_alpha.png │ │ ├── WebKitSVG2PNGTests │ │ │ ├── svgWithGradient.gradient-svg.png │ │ │ ├── simpleSVGConversion.simple-svg.png │ │ │ └── complexSVGFromTestSuite.gradient-from-suite.png │ │ └── CodeGenerationSnapshotTests │ │ │ ├── gradientDeterminismGeneration.2.h │ │ │ ├── pdfExtGStateDeterminismGeneration.2.h │ │ │ ├── pathGeneration.2.h │ │ │ ├── mixedGeneration.2.h │ │ │ ├── pluginDemoGeneration.2.h │ │ │ ├── pdfExtGStateDeterminismGeneration.1.swift │ │ │ ├── pdfExtGStateDeterminismGeneration.3.m │ │ │ ├── gradientDeterminismGeneration.1.swift │ │ │ ├── mixedGeneration.1.swift │ │ │ ├── pluginDemoGeneration.1.swift │ │ │ ├── gradientDeterminismGeneration.3.m │ │ │ ├── mixedGeneration.3.m │ │ │ ├── pluginDemoGeneration.3.m │ │ │ ├── pathGeneration.1.swift │ │ │ └── pathGeneration.3.m │ ├── various_filenames │ │ ├── dash-dash.svg │ │ ├── Capital letter.svg │ │ ├── under_score.svg │ │ └── white space.svg │ ├── svg_samples │ │ ├── path_short_commands.svg │ │ ├── path_relative_commands.svg │ │ ├── path_smooth_curve.svg │ │ ├── topmost_presentation_attributes.svg │ │ ├── fill.svg │ │ ├── lines_and_curves.svg │ │ ├── paths_and_images.svg │ │ ├── path_fill_rule_nonzero_default.svg │ │ ├── path_move_to_commands.svg │ │ ├── use_referencing_not_in_defs.svg │ │ ├── gradient_stroke.svg │ │ ├── miter_limit.svg │ │ ├── white_cross_scn_operator.svg │ │ ├── gradient_absolute_start_end.svg │ │ ├── gradient_radial.svg │ │ ├── path_complex_curve.svg │ │ ├── lines.svg │ │ ├── gradient.svg │ │ ├── group_opacity.svg │ │ ├── gradient_relative.svg │ │ ├── gradient_shape.svg │ │ ├── shadow_simple.svg │ │ ├── path_quadratic_bezier.svg │ │ ├── gradient_three_dots.svg │ │ ├── clip_path.svg │ │ ├── use_tag.svg │ │ ├── gradient_transform_radial.svg │ │ ├── gradient_transform_linear.svg │ │ ├── gradient_units.svg │ │ ├── path_circle_commands.svg │ │ ├── alpha.svg │ │ ├── gradient_with_alpha.svg │ │ ├── simple_mask.svg │ │ ├── caps_joins.svg │ │ ├── path_fill_rule.svg │ │ ├── path_fill_rule_gstate.svg │ │ ├── underlying_object_with_tiny_alpha.svg │ │ ├── gradient_fill_stroke_combinations.svg │ │ ├── shapes.svg │ │ ├── gradient_determinism_test.svg │ │ ├── gradient_with_mask.svg │ │ ├── gradient_opacity.svg │ │ ├── shadow_blur_radius.svg │ │ ├── dashes.svg │ │ ├── shadow_colors.svg │ │ └── transforms.svg │ ├── RegressionSuite.xctestplan │ ├── SVGRuntimeRenderingTests.swift │ ├── BCCompilationTests.swift │ └── PDFTests.swift ├── UnitTests │ ├── UnitTests.xctestplan │ ├── SplitByTests.swift │ ├── XMLRenderTests.swift │ ├── ParserTests.swift │ ├── BaseTests.swift │ └── XMLParserTests.swift └── CGGenTests │ └── SVGRendererTests.swift ├── test.playground ├── playground.xcworkspace │ └── contents.xcworkspacedata └── contents.xcplayground ├── .gitignore ├── Sources ├── plugin-demo │ ├── circle.svg │ ├── square.svg │ ├── star.svg │ ├── paths.svg │ └── SwiftUIExample.swift ├── CGGenIR │ ├── Common.swift │ ├── PathSegment.swift │ ├── Types.swift │ └── Routines.swift ├── PDFParse │ ├── Error.swift │ ├── PDFParser.swift │ ├── PDFPage.swift │ ├── PDFResources.swift │ ├── PDFStream.swift │ ├── PDFExtGState.swift │ ├── PDFOperator.swift │ └── PDFXObject.swift ├── CGGenCLI │ ├── Image.swift │ ├── Common.swift │ ├── GenerationParams.swift │ ├── ObjCGen.swift │ └── ObjcHeaderCGGenerator.swift ├── CGGenDiagnosticSupport │ ├── Common.swift │ └── ImageComparison.swift ├── cggen-diagnostic │ └── CGGenDiagnostic.swift ├── CGGenCore │ ├── StopWatch.swift │ └── Logger.swift ├── CGGenBytecodeDecoding │ └── Decompressor.swift ├── SVGParse │ ├── SVGShapeParser.swift │ └── SVGAttributeGroupParser.swift ├── cggen │ └── CGGen.swift ├── CGGenRTSupport │ ├── PlatformImageSupport+Deprecated.swift │ ├── README.md │ └── CGImageSupport.swift └── CGGenRuntime │ ├── SVGRenderer.swift │ └── SVGSupport.swift ├── cggen.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── WorkspaceSettings.xcsettings │ ├── IDEWorkspaceChecks.plist │ ├── swiftpm │ └── Package.resolved │ └── xcschemes │ └── RegressionTests.xcscheme ├── AUTHORS ├── .swiftformat ├── LICENSE ├── install-hooks.sh ├── git-hooks └── pre-commit ├── .github └── workflows │ ├── swift.yml │ ├── update-webkit-snapshots.yml │ └── nightly-extended-tests.yml ├── CONTRIBUTING ├── CHANGELOG.md ├── Package.resolved └── .claude └── commands └── create-release.md /Podspecs/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /Podspecs/Sources: -------------------------------------------------------------------------------- 1 | ../Sources -------------------------------------------------------------------------------- /.periphery.yml: -------------------------------------------------------------------------------- 1 | retain_assign_only_properties: true 2 | retain_public: true 3 | -------------------------------------------------------------------------------- /Demo/cggendemo.shortcut: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Demo/cggendemo.shortcut -------------------------------------------------------------------------------- /Tests/RegressionTests/tests.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/tests.sketch -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/fill.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/fill.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/alpha.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/alpha.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/dashes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/dashes.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/lines.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/lines.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/shapes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/shapes.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/gradient.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/gradient.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/caps_joins.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/caps_joins.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/gradient_shape.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/gradient_shape.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/group_opacity.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/group_opacity.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/gradient_radial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/gradient_radial.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/gradient_three_dots.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/gradient_three_dots.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/gradient_with_alpha.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/gradient_with_alpha.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/gradient_with_mask.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/gradient_with_mask.pdf -------------------------------------------------------------------------------- /test.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/nested_transparent_group.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/nested_transparent_group.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata 6 | timeline.xctimeline 7 | /build 8 | *.pyc 9 | .swiftpm 10 | .idea 11 | .vscode 12 | -------------------------------------------------------------------------------- /Sources/plugin-demo/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.alpha.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.fill.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.lines.png -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/underlying_object_with_tiny_alpha.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/pdf_samples/underlying_object_with_tiny_alpha.pdf -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.dashes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.dashes.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.shapes.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.use_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.use_tag.png -------------------------------------------------------------------------------- /Sources/CGGenIR/Common.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct GenericError: Swift.Error { 4 | var desc: String 5 | 6 | init(_ desc: String) { 7 | self.desc = desc 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.caps_joins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.caps_joins.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.clip_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.clip_path.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.colornames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.colornames.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.transforms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.transforms.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.group_opacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.group_opacity.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.miter_limit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.miter_limit.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.shadow_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.shadow_colors.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.shadow_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.shadow_simple.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.simple_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.simple_mask.png -------------------------------------------------------------------------------- /cggen.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_radial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_radial.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_shape.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_stroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_stroke.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_units.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_units.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_fill_rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_fill_rule.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_opacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_opacity.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_relative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_relative.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_with_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_with_mask.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.lines_and_curves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.lines_and_curves.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_complex_curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_complex_curve.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_smooth_curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_smooth_curve.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.paths_and_images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.paths_and_images.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.shadow_blur_radius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.shadow_blur_radius.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_three_dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_three_dots.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_with_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_with_alpha.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_circle_commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_circle_commands.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_short_commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_short_commands.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/WebKitSVG2PNGTests/svgWithGradient.gradient-svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/WebKitSVG2PNGTests/svgWithGradient.gradient-svg.png -------------------------------------------------------------------------------- /test.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/PDFParse/Error.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Error: Swift.Error { 4 | case parsingError(file: String = #file, line: Int = #line) 5 | case unsupported(String, file: String = #file, line: Int = #line) 6 | } 7 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_fill_rule_gstate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_fill_rule_gstate.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_move_to_commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_move_to_commands.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_quadratic_bezier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_quadratic_bezier.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_relative_commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_relative_commands.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/WebKitSVG2PNGTests/simpleSVGConversion.simple-svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/WebKitSVG2PNGTests/simpleSVGConversion.simple-svg.png -------------------------------------------------------------------------------- /Sources/CGGenCLI/Image.swift: -------------------------------------------------------------------------------- 1 | import CGGenIR 2 | 3 | struct Output { 4 | var image: Image 5 | var pathRoutines: [PathRoutine] 6 | } 7 | 8 | struct Image { 9 | let name: String 10 | let route: DrawRoutine 11 | } 12 | -------------------------------------------------------------------------------- /Sources/plugin-demo/square.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_determinism_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_determinism_test.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_transform_linear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_transform_linear.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_transform_radial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_transform_radial.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.nested_transparent_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.nested_transparent_group.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.white_cross_scn_operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.white_cross_scn_operator.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_absolute_start_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_absolute_start_end.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.use_referencing_not_in_defs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.use_referencing_not_in_defs.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_fill_rule_nonzero_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.path_fill_rule_nonzero_default.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_fill_stroke_combinations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.gradient_fill_stroke_combinations.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.topmost_presentation_attributes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.topmost_presentation_attributes.png -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.underlying_object_with_tiny_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/SVGTests/webkit-references.underlying_object_with_tiny_alpha.png -------------------------------------------------------------------------------- /Sources/plugin-demo/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/WebKitSVG2PNGTests/complexSVGFromTestSuite.gradient-from-suite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandex/cggen/main/Tests/RegressionTests/__Snapshots__/WebKitSVG2PNGTests/complexSVGFromTestSuite.gradient-from-suite.png -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following authors have created the source code of "cggen" published and distributed by YANDEX LLC as the owner: 2 | 3 | Alfred Zien zienag@yandex-team.ru 4 | Alexander Skvortsov askvortsov@yandex-team.ru 5 | Bulat Galiev bgaliev@yandex-team.ru 6 | -------------------------------------------------------------------------------- /Demo/Demo/Images/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Demo/Demo/Images/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Sources/CGGenDiagnosticSupport/Common.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Simple error type matching the one from tests 4 | public struct Err: Swift.Error { 5 | public var description: String 6 | 7 | public init(_ desc: String) { 8 | description = desc 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /cggen.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /cggen.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/RegressionTests/various_filenames/dash-dash.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /Tests/RegressionTests/various_filenames/Capital letter.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /Tests/RegressionTests/various_filenames/under_score.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /Tests/RegressionTests/various_filenames/white space.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /Demo/Demo/Demo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Sources/cggen-diagnostic/CGGenDiagnostic.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | 4 | @main 5 | struct CGGenDiagnostic: AsyncParsableCommand { 6 | static let configuration = CommandConfiguration( 7 | commandName: "cggen-diagnostic", 8 | abstract: "Diagnostic tools for cggen", 9 | subcommands: [RenderCompareCommand.self, DecodeCommand.self] 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_short_commands.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --disable wrapMultilineStatementBraces 2 | --extensionacl on-declarations 3 | --hexgrouping 2 4 | --ifdef noindent 5 | --indent 2 6 | --operatorfunc nospace 7 | --ranges nospace 8 | --swiftversion 6.0 9 | --symlinks ignore 10 | --wraparguments before-first 11 | --wrapcollections before-first 12 | --wrapparameters before-first 13 | --maxwidth 80 14 | --exclude Tests/RegressionTests/__Snapshots__ 15 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_relative_commands.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_smooth_curve.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sources/PDFParse/PDFParser.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | 4 | public enum PDFParser { 5 | public static func parse(pdfURL: CFURL) throws -> [PDFPage] { 6 | guard let pdfDoc = CGPDFDocument(pdfURL) else { 7 | throw Error.parsingError() 8 | } 9 | return try pdfDoc.pages.map(PDFPage.init(page:)) 10 | } 11 | } 12 | 13 | extension CGPDFDocument { 14 | var pages: [CGPDFPage] { 15 | (1...numberOfPages).map { page(at: $0)! } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/gradientDeterminismGeneration.2.h: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | #if __has_feature(modules) 4 | @import CoreGraphics; 5 | #else // __has_feature(modules) 6 | #import 7 | #endif // __has_feature(modules) 8 | 9 | static const CGSize kgrad_determGradientDeterminismTestImageSize = (CGSize){.width = 50.0, .height = 50.0}; 10 | void grad_determDrawGradientDeterminismTestImageInContext(CGContextRef context); 11 | 12 | -------------------------------------------------------------------------------- /Tests/UnitTests/UnitTests.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "9C1D9B74-8DE1-4B1D-A757-F554717E30CC", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | 13 | }, 14 | "testTargets" : [ 15 | { 16 | "target" : { 17 | "containerPath" : "container:", 18 | "identifier" : "UnitTests", 19 | "name" : "UnitTests" 20 | } 21 | } 22 | ], 23 | "version" : 1 24 | } 25 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pdfExtGStateDeterminismGeneration.2.h: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | #if __has_feature(modules) 4 | @import CoreGraphics; 5 | #else // __has_feature(modules) 6 | #import 7 | #endif // __has_feature(modules) 8 | 9 | static const CGSize kpdf_extgstate_determExtgstateMultipleParamsImageSize = (CGSize){.width = 200.0, .height = 200.0}; 10 | void pdf_extgstate_determDrawExtgstateMultipleParamsImageInContext(CGContextRef context); 11 | 12 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/topmost_presentation_attributes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/UnitTests/SplitByTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | 3 | import CGGenCore 4 | 5 | @Suite struct SplitByTests { 6 | @Test func testSplitBy() { 7 | #expect( 8 | Array([0, 1, 2, 3].splitBy(subSize: 2)) == 9 | [[0, 1], [2, 3]] 10 | ) 11 | } 12 | 13 | @Test func splitSubscript() { 14 | let xs = [0, 1, 2, 3, 4, 5].splitBy(subSize: 3) 15 | #expect(xs[0].startIndex == 0) 16 | #expect(xs[0].endIndex == 3) 17 | #expect(xs[1].startIndex == 3) 18 | #expect(xs[1].endIndex == 6) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/RegressionTests/RegressionSuite.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "5FA72C2F-292D-4F8D-8E10-56C12F063776", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | 13 | }, 14 | "testTargets" : [ 15 | { 16 | "target" : { 17 | "containerPath" : "container:", 18 | "identifier" : "RegressionTests", 19 | "name" : "RegressionTests" 20 | } 21 | } 22 | ], 23 | "version" : 1 24 | } 25 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fill 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/lines_and_curves.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/paths_and_images.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Sources/CGGenCLI/Common.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | let commonHeaderPrefix = "// Generated by cggen" 4 | 5 | public enum GenerationStyle: String { 6 | case plain 7 | case swiftFriendly = "swift-friendly" 8 | } 9 | 10 | extension GenerationStyle { 11 | var drawingHandlerPrefix: String { 12 | switch self { 13 | case .plain: 14 | "" 15 | case .swiftFriendly: 16 | "static " 17 | } 18 | } 19 | } 20 | 21 | struct GenericError: Swift.Error { 22 | var desc: String 23 | 24 | init(_ desc: String) { 25 | self.desc = desc 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_fill_rule_nonzero_default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /Sources/CGGenCore/StopWatch.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct StopWatch { 4 | public struct Result: CustomStringConvertible { 5 | public let time: TimeInterval 6 | public var description: String { 7 | "\(time)" 8 | } 9 | } 10 | 11 | var started = Date() 12 | public init() {} 13 | 14 | public func lap() -> Result { 15 | Result(time: Date().timeIntervalSince(started)) 16 | } 17 | 18 | public mutating func reset() -> Result { 19 | let prevStarted = started 20 | started = Date() 21 | return Result(time: Date().timeIntervalSince(prevStarted)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pathGeneration.2.h: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | #if __has_feature(modules) 4 | @import CoreGraphics; 5 | #else // __has_feature(modules) 6 | #import 7 | #endif // __has_feature(modules) 8 | 9 | static const CGSize ktestPathsImageSize = (CGSize){.width = 100.0, .height = 100.0}; 10 | void testDrawPathsImageInContext(CGContextRef context); 11 | 12 | void testSimpleArrowPath(CGMutablePathRef path); 13 | 14 | void testSimpleHeartPath(CGMutablePathRef path); 15 | 16 | void testSimpleStarPath(CGMutablePathRef path); 17 | 18 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_move_to_commands.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 YANDEX LLC 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/mixedGeneration.2.h: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | #if __has_feature(modules) 4 | @import CoreGraphics; 5 | #else // __has_feature(modules) 6 | #import 7 | #endif // __has_feature(modules) 8 | 9 | static const CGSize kmixedPathsAndImagesImageSize = (CGSize){.width = 50.0, .height = 50.0}; 10 | void mixedDrawPathsAndImagesImageInContext(CGContextRef context); 11 | 12 | void mixedDiamondPath(CGMutablePathRef path); 13 | 14 | void mixedHexagonPath(CGMutablePathRef path); 15 | 16 | void mixedTrianglePath(CGMutablePathRef path); 17 | 18 | -------------------------------------------------------------------------------- /install-hooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the directory where this script is located 4 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 5 | cd "$SCRIPT_DIR" 6 | 7 | HOOKS_DIR="git-hooks" 8 | GIT_HOOKS_DIR=".git/hooks" 9 | 10 | mkdir -p "$GIT_HOOKS_DIR" 11 | 12 | for hook in "$HOOKS_DIR"/*; do 13 | if [ -f "$hook" ]; then 14 | hook_name=$(basename "$hook") 15 | target="$GIT_HOOKS_DIR/$hook_name" 16 | 17 | if [ -e "$target" ] || [ -L "$target" ]; then 18 | rm "$target" 19 | fi 20 | 21 | ln -s "$SCRIPT_DIR/$HOOKS_DIR/$hook_name" "$target" 22 | fi 23 | done 24 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/use_referencing_not_in_defs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_stroke.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/miter_limit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Sources/CGGenBytecodeDecoding/Decompressor.swift: -------------------------------------------------------------------------------- 1 | import Compression 2 | import Foundation 3 | 4 | public func decompressBytecode( 5 | _ start: UnsafePointer, 6 | _ compressedLen: Int, 7 | _ decompressedLen: Int 8 | ) throws -> [UInt8] { 9 | let decodedDestinationBuffer = UnsafeMutablePointer 10 | .allocate(capacity: decompressedLen) 11 | 12 | let decompressedSize = compression_decode_buffer( 13 | decodedDestinationBuffer, 14 | decompressedLen, 15 | start, 16 | compressedLen, 17 | nil, 18 | COMPRESSION_LZFSE 19 | ) 20 | 21 | return [UInt8](UnsafeBufferPointer( 22 | start: decodedDestinationBuffer, 23 | count: decompressedSize 24 | )) 25 | } 26 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/white_cross_scn_operator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | white_cross_scn_operator 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/CGGenIR/PathSegment.swift: -------------------------------------------------------------------------------- 1 | @preconcurrency import CoreGraphics 2 | 3 | public enum PathSegment: Sendable { 4 | case moveTo(CGPoint) 5 | case curveTo(CGPoint, CGPoint, CGPoint) 6 | case quadCurveTo(CGPoint, CGPoint) // control, to 7 | case lineTo(CGPoint) 8 | case appendRectangle(CGRect) 9 | case appendRoundedRect(CGRect, rx: CGFloat, ry: CGFloat) 10 | case addEllipse(in: CGRect) 11 | case addArc( 12 | center: CGPoint, 13 | radius: CGFloat, 14 | startAngle: CGFloat, 15 | endAngle: CGFloat, 16 | clockwise: Bool 17 | ) 18 | case closePath 19 | case endPath 20 | 21 | case lines([CGPoint]) 22 | case composite([PathSegment]) 23 | 24 | public static let empty: PathSegment = .composite([]) 25 | } 26 | -------------------------------------------------------------------------------- /Sources/plugin-demo/paths.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_absolute_start_end.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_radial.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_complex_curve.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | complex_bezie 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pluginDemoGeneration.2.h: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | #if __has_feature(modules) 4 | @import CoreGraphics; 5 | #else // __has_feature(modules) 6 | #import 7 | #endif // __has_feature(modules) 8 | 9 | static const CGSize kplugin_demoCircleImageSize = (CGSize){.width = 50.0, .height = 50.0}; 10 | void plugin_demoDrawCircleImageInContext(CGContextRef context); 11 | 12 | static const CGSize kplugin_demoSquareImageSize = (CGSize){.width = 40.0, .height = 40.0}; 13 | void plugin_demoDrawSquareImageInContext(CGContextRef context); 14 | 15 | static const CGSize kplugin_demoStarImageSize = (CGSize){.width = 60.0, .height = 60.0}; 16 | void plugin_demoDrawStarImageInContext(CGContextRef context); 17 | 18 | -------------------------------------------------------------------------------- /Demo/Demo/Images/gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Tests/UnitTests/XMLRenderTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | 3 | import CGGenCore 4 | 5 | @Suite struct XMLRenderTests { 6 | @Test func simpleXML() { 7 | #expect( 8 | XML.el("note", children: [ 9 | .el("to", children: [.text("Tove")]), 10 | .el("from", children: [.text("Jani")]), 11 | .el("body", children: [.text("Hello world!")]), 12 | ]).render() == 13 | "ToveJaniHello world!" 14 | ) 15 | } 16 | 17 | @Test func xMLWithAttributes() { 18 | #expect( 19 | XML.el("rect", attrs: ["size": "10,20"], children: [ 20 | .el("square", attrs: ["size": "5"]), 21 | .text("Hello"), 22 | ]).render() == 23 | #"Hello"# 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/CGGenCLI/GenerationParams.swift: -------------------------------------------------------------------------------- 1 | struct GenerationParams { 2 | let style: GenerationStyle 3 | let prefix: String 4 | let module: String 5 | } 6 | 7 | extension GenerationParams { 8 | private func funcName(imageName: String) -> String { 9 | ObjCGen.functionName(imageName: imageName.upperCamelCase, prefix: prefix) 10 | } 11 | 12 | func descriptorLines(for image: Image) -> [String] { 13 | switch style { 14 | case .plain: 15 | return [] 16 | case .swiftFriendly: 17 | let size = image.route.boundingRect.size 18 | return [ 19 | "const \(descriptorTypename) \(descriptorName(for: image)) = {", 20 | " { (CGFloat)\(size.width), (CGFloat)\(size.height) },", 21 | " \(funcName(imageName: image.name))", 22 | "};", 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/lines.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lines 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gradient 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Check for whitespace errors 4 | git diff --cached --check 5 | if [ $? -ne 0 ]; then 6 | echo "❌ Whitespace errors found. Please fix them." 7 | exit 1 8 | fi 9 | 10 | # Check for missing newline at EOF in text files 11 | for file in $(git diff --cached --name-only --diff-filter=ACMR); do 12 | if [ -f "$file" ] && [ -s "$file" ]; then 13 | # Check if file is text (not binary) 14 | if file "$file" | grep -q text; then 15 | if [ -n "$(tail -c 1 "$file")" ]; then 16 | echo "❌ Missing newline at end of file: $file" 17 | exit 1 18 | fi 19 | fi 20 | fi 21 | done 22 | 23 | # Check swiftformat 24 | OUTPUT=$(swiftformat --lint . 2>&1) 25 | if [ $? -ne 0 ]; then 26 | echo "❌ SwiftFormat issues found. Please run 'swiftformat .' to fix them." 27 | echo "$OUTPUT" 28 | exit 1 29 | fi 30 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/group_opacity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_relative.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gradient_relative 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_shape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gradient_shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pdfExtGStateDeterminismGeneration.1.swift: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | import CoreGraphics 4 | @_spi(Generator) import CGGenRTSupport 5 | 6 | typealias Drawing = CGGenRTSupport.Drawing 7 | 8 | extension Drawing { 9 | static let extgstateMultipleParams = Drawing( 10 | width: 200.0, 11 | height: 200.0, 12 | bytecodeArray: mergedBytecodes, 13 | decompressedSize: 41, 14 | startIndex: 0, 15 | endIndex: 40 16 | ) 17 | } 18 | 19 | private let mergedBytecodes: [UInt8] = [ 20 | 0x62, 0x76, 0x78, 0x2D, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x40, 0x3F, 0x25, 0x00, 0x00, 22 | 0x00, 0x3F, 0x24, 0x7F, 0x7F, 0x7F, 0x06, 0x00, 0x00, 0xA0, 0x41, 0x00, 23 | 0x00, 0xA0, 0x41, 0x00, 0x00, 0x20, 0x43, 0x00, 0x00, 0x20, 0x43, 0x13, 24 | 0x00, 0x62, 0x76, 0x78, 0x24 25 | ] 26 | -------------------------------------------------------------------------------- /Podspecs/CGGenBytecode.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CGGenBytecode' 3 | s.version = '1.1.3' 4 | s.summary = 'Bytecode definitions for cggen vector graphics' 5 | s.description = <<-DESC 6 | Low-level bytecode command definitions and data structures for cggen. 7 | This is an internal dependency of CGGenRTSupport. 8 | DESC 9 | 10 | s.homepage = 'https://github.com/yandex/cggen' 11 | s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } 12 | s.author = 'Yandex LLC' 13 | s.source = { :git => 'https://github.com/yandex/cggen.git', :tag => s.version.to_s } 14 | 15 | s.ios.deployment_target = '13.0' 16 | s.osx.deployment_target = '14.0' 17 | 18 | s.swift_versions = ['5.9', '6.0'] 19 | 20 | s.source_files = 'Sources/CGGenBytecode/**/*.swift' 21 | 22 | s.frameworks = 'Foundation', 'CoreGraphics' 23 | end 24 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/shadow_simple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Sources/CGGenDiagnosticSupport/ImageComparison.swift: -------------------------------------------------------------------------------- 1 | import CGGenCore 2 | import CoreGraphics 3 | import Foundation 4 | 5 | /// Image comparison utilities for diagnostic support 6 | public enum ImageComparison { 7 | /// Compare two images and return the root mean square difference 8 | /// Returns a value between 0.0 (identical) and higher values for more 9 | /// different images 10 | @_optimize(speed) 11 | public static func compare(_ img1: CGImage, _ img2: CGImage) -> Double { 12 | let buffer1 = RGBABuffer(image: img1) 13 | let buffer2 = RGBABuffer(image: img2) 14 | 15 | let rw1 = buffer1.pixels 16 | .flatMap(\.self) 17 | .flatMap { $0.norm(Double.self).components } 18 | 19 | let rw2 = buffer2.pixels 20 | .flatMap(\.self) 21 | .flatMap { $0.norm(Double.self).components } 22 | 23 | let ziped = zip(rw1, rw2).lazy.map(-) 24 | return ziped.rootMeanSquare() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/RegressionTests/SVGRuntimeRenderingTests.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | import Testing 4 | 5 | import CGGenCore 6 | import CGGenRuntime 7 | import SnapshotTesting 8 | 9 | @Suite struct SVGRuntimeRenderingTests { 10 | @Test("SVG Runtime Rendering", arguments: SVGTestCase.smokeTestSubset) 11 | func svgRuntimeRendering(testCase: SVGTestCase) throws { 12 | let svgURL = svgSamplesPath 13 | .appendingPathComponent(testCase.rawValue) 14 | .appendingPathExtension("svg") 15 | 16 | let svgData = try Data(contentsOf: svgURL) 17 | 18 | // Render at 2x scale 19 | let runtimeImage = try CGImage.svg( 20 | svgData, 21 | scale: 2.0 22 | ) 23 | 24 | // Compare against the same webkit-references used by bytecode tests 25 | assertAgainstReference( 26 | image: runtimeImage.redraw(with: CGColor.white), 27 | testCase: testCase 28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/PDFParse/PDFPage.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | import CGGenCore 4 | 5 | public typealias PDFColor = RGBColor 6 | 7 | public struct PDFPage { 8 | public let resources: PDFResources 9 | public let operators: [PDFOperator] 10 | public let bbox: CGRect 11 | 12 | init(page: CGPDFPage) throws { 13 | let stream = CGPDFContentStreamCreateWithPage(page) 14 | let operators = try PDFContentStreamParser 15 | .parse(stream: CGPDFContentStreamCreateWithPage(page)) 16 | 17 | guard let pageDictRaw = page.dictionary, 18 | let pageDictionary = PDFObject.processDict(pageDictRaw)["Resources"] 19 | else { throw Error.parsingError() } 20 | let resources = try PDFResources(obj: pageDictionary, parentStream: stream) 21 | 22 | let bbox = page.getBoxRect(.mediaBox) 23 | 24 | self.operators = operators 25 | self.resources = resources 26 | self.bbox = bbox 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/extgstate_multiple_params.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.4 2 | 1 0 obj 3 | << 4 | /Type /Catalog 5 | /Pages 2 0 R 6 | >> 7 | endobj 8 | 9 | 2 0 obj 10 | << 11 | /Type /Pages 12 | /Kids [3 0 R] 13 | /Count 1 14 | >> 15 | endobj 16 | 17 | 3 0 obj 18 | << 19 | /Type /Page 20 | /Parent 2 0 R 21 | /MediaBox [0 0 200 200] 22 | /Resources << 23 | /ExtGState << 24 | /GS1 4 0 R 25 | >> 26 | >> 27 | /Contents 5 0 R 28 | >> 29 | endobj 30 | 31 | 4 0 obj 32 | << 33 | /Type /ExtGState 34 | /ca 0.5 35 | /CA 0.75 36 | >> 37 | endobj 38 | 39 | 5 0 obj 40 | << 41 | /Length 50 42 | >> 43 | stream 44 | /GS1 gs 45 | 0.5 0.5 0.5 rg 46 | 20 20 160 160 re 47 | f 48 | endstream 49 | endobj 50 | 51 | xref 52 | 0 6 53 | 0000000000 65535 f 54 | 0000000009 00000 n 55 | 0000000058 00000 n 56 | 0000000115 00000 n 57 | 0000000264 00000 n 58 | 0000000323 00000 n 59 | trailer 60 | << 61 | /Size 6 62 | /Root 1 0 R 63 | >> 64 | startxref 65 | 423 66 | %%EOF -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_quadratic_bezier.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | 17 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /Sources/PDFParse/PDFResources.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | import CGGenCore 4 | 5 | public struct PDFResources { 6 | public let shadings: [String: PDFShading] 7 | public let gStates: [String: PDFExtGState] 8 | public let xObjects: [String: PDFXObject] 9 | 10 | init(obj: PDFObject, parentStream: CGPDFContentStreamRef) throws { 11 | guard case let .dictionary(dict) = obj 12 | else { throw Error.parsingError() } 13 | let xobjFactory = partial(PDFXObject.init, arg2: parentStream) 14 | let shadingDict = dict["Shading"]?.dictionaryVal() ?? [:] 15 | let gStatesDict = dict["ExtGState"]?.dictionaryVal() ?? [:] 16 | let xObjectsDict = dict["XObject"]?.dictionaryVal() ?? [:] 17 | 18 | shadings = try shadingDict.mapValues { try PDFShading(obj: $0) } 19 | gStates = try gStatesDict 20 | .mapValues(partial(PDFExtGState.init, arg2: xobjFactory)) 21 | xObjects = try xObjectsDict.mapValues(xobjFactory) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_three_dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gradient_three_dots 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Podspecs/CGGenBytecodeDecoding.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CGGenBytecodeDecoding' 3 | s.version = '1.1.3' 4 | s.summary = 'Bytecode decoding for cggen vector graphics' 5 | s.description = <<-DESC 6 | Bytecode decompression and visitor pattern implementation for cggen. 7 | This is an internal dependency of CGGenRTSupport. 8 | DESC 9 | 10 | s.homepage = 'https://github.com/yandex/cggen' 11 | s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } 12 | s.author = 'Yandex LLC' 13 | s.source = { :git => 'https://github.com/yandex/cggen.git', :tag => s.version.to_s } 14 | 15 | s.ios.deployment_target = '13.0' 16 | s.osx.deployment_target = '14.0' 17 | 18 | s.swift_versions = ['5.9', '6.0'] 19 | 20 | s.source_files = 'Sources/CGGenBytecodeDecoding/**/*.swift' 21 | 22 | s.frameworks = 'Foundation' 23 | s.libraries = 'compression' 24 | 25 | s.dependency 'CGGenBytecode', s.version.to_s 26 | end 27 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/clip_path.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/use_tag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Sources/CGGenIR/Types.swift: -------------------------------------------------------------------------------- 1 | import CGGenCore 2 | @preconcurrency import CoreGraphics 3 | import Foundation 4 | 5 | public typealias RGBACGColor = RGBAColor 6 | public typealias RGBCGColor = RGBColor 7 | 8 | public struct Gradient: Sendable { 9 | public var locationAndColors: [(CGFloat, RGBACGColor)] 10 | 11 | public init(locationAndColors: [(CGFloat, RGBACGColor)]) { 12 | self.locationAndColors = locationAndColors 13 | } 14 | } 15 | 16 | public struct Shadow: Sendable { 17 | public var offset: CGSize 18 | public var blur: CGFloat 19 | public var color: RGBACGColor 20 | 21 | public init(offset: CGSize, blur: CGFloat, color: RGBACGColor) { 22 | self.offset = offset 23 | self.blur = blur 24 | self.color = color 25 | } 26 | } 27 | 28 | public struct DashPattern: Sendable { 29 | public let phase: CGFloat 30 | public let lengths: [CGFloat] 31 | 32 | public init(phase: CGFloat, lengths: [CGFloat]) { 33 | self.phase = phase 34 | self.lengths = lengths 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_transform_radial.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_transform_linear.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_units.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_circle_commands.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/alpha.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | alpha 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demo/Demo/Images/mountain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Sources/plugin-demo/SwiftUIExample.swift: -------------------------------------------------------------------------------- 1 | #if canImport(SwiftUI) 2 | import SwiftUI 3 | 4 | struct SwiftUIExample: View { 5 | var body: some View { 6 | VStack(spacing: 20) { 7 | Text("cggen SwiftUI Example") 8 | .font(.title) 9 | 10 | // Direct usage as SwiftUI Views 11 | HStack(spacing: 30) { 12 | Drawing.circle 13 | .foregroundColor(.blue) 14 | 15 | Drawing.square 16 | .foregroundColor(.green) 17 | 18 | Drawing.star 19 | .foregroundColor(.orange) 20 | } 21 | 22 | // With standard modifiers 23 | Drawing.star 24 | .frame(width: 60, height: 60) 25 | .foregroundColor(.yellow) 26 | .shadow(radius: 2) 27 | 28 | // In buttons 29 | Button(action: {}) { 30 | HStack { 31 | Drawing.circle 32 | .frame(width: 20, height: 20) 33 | Text("Button") 34 | } 35 | } 36 | .buttonStyle(.borderedProminent) 37 | } 38 | .padding() 39 | } 40 | } 41 | 42 | #Preview { 43 | SwiftUIExample() 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pdfExtGStateDeterminismGeneration.3.m: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | void runMergedBytecode(CGContextRef context, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 4 | void runPathBytecode(CGMutablePathRef path, const uint8_t* arr, int len); 5 | void runMergedPathBytecode(CGMutablePathRef path, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 6 | 7 | static const uint8_t mergedBytecodes[]; 8 | 9 | void pdf_extgstate_determDrawExtgstateMultipleParamsImageInContext(CGContextRef context) { 10 | runMergedBytecode(context, mergedBytecodes, 41, 53, 0, 40); 11 | } 12 | 13 | static const uint8_t mergedBytecodes[] = { 14 | 0x62, 0x76, 0x78, 0x2D, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x40, 0x3F, 0x25, 0x00, 0x00, 16 | 0x00, 0x3F, 0x24, 0x7F, 0x7F, 0x7F, 0x06, 0x00, 0x00, 0xA0, 0x41, 0x00, 17 | 0x00, 0xA0, 0x41, 0x00, 0x00, 0x20, 0x43, 0x00, 0x00, 0x20, 0x43, 0x13, 18 | 0x00, 0x62, 0x76, 0x78, 0x24 19 | }; 20 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_with_alpha.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Podspecs/CGGenRTSupport.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CGGenRTSupport' 3 | s.version = '1.1.3' 4 | s.summary = 'Runtime support for cggen-generated vector graphics code' 5 | s.description = <<-DESC 6 | Runtime library for executing cggen-generated bytecode. Provides the Drawing 7 | struct and utilities for creating UIImage/NSImage/SwiftUI.Image from vector 8 | graphics generated by cggen CLI tool. 9 | DESC 10 | 11 | s.homepage = 'https://github.com/yandex/cggen' 12 | s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } 13 | s.author = 'Yandex LLC' 14 | s.source = { :git => 'https://github.com/yandex/cggen.git', :tag => s.version.to_s } 15 | 16 | s.ios.deployment_target = '13.0' 17 | s.osx.deployment_target = '14.0' 18 | 19 | s.swift_versions = ['5.9', '6.0'] 20 | 21 | s.source_files = 'Sources/CGGenRTSupport/**/*.swift' 22 | 23 | s.ios.frameworks = 'UIKit', 'CoreGraphics', 'SwiftUI' 24 | s.osx.frameworks = 'AppKit', 'CoreGraphics', 'SwiftUI' 25 | 26 | s.dependency 'CGGenBytecodeDecoding', s.version.to_s 27 | end 28 | -------------------------------------------------------------------------------- /Tests/UnitTests/ParserTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | 3 | import CGGenCore 4 | @preconcurrency import Parsing 5 | 6 | @Suite struct ParserTests { 7 | @Test func dictionaryKey() throws { 8 | var dict = ["a": "1", "b": "2", "x": "99", "y": "100"] 9 | 10 | // Test throws when key not found 11 | #expect(throws: ParseError.self) { 12 | _ = try DicitionaryKey("missing").parse(&dict) 13 | } 14 | 15 | // Test returns value when key exists 16 | let result = try DicitionaryKey("a").parse(&dict) 17 | #expect(result == "1") 18 | #expect(dict == ["b": "2", "x": "99", "y": "100"]) 19 | 20 | // Test Optionally returns nil for missing key 21 | let optional1 = Optionally { DicitionaryKey("c") } 22 | .parse(&dict) 23 | #expect(optional1 == nil) 24 | #expect(dict == ["b": "2", "x": "99", "y": "100"]) 25 | 26 | // Test Optionally returns value for existing key 27 | let optional2 = Optionally { DicitionaryKey("b") } 28 | .parse(&dict) 29 | #expect(optional2 == "2") 30 | #expect(dict == ["x": "99", "y": "100"]) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/CGGenIR/Routines.swift: -------------------------------------------------------------------------------- 1 | @preconcurrency import CoreGraphics 2 | 3 | public struct DrawRoutine: Sendable { 4 | public var boundingRect: CGRect 5 | public var gradients: [String: Gradient] 6 | public var subroutines: [String: DrawRoutine] 7 | public var steps: [DrawStep] 8 | 9 | public init( 10 | boundingRect: CGRect, 11 | gradients: [String: Gradient], 12 | subroutines: [String: DrawRoutine], 13 | steps: [DrawStep] 14 | ) { 15 | self.boundingRect = boundingRect 16 | self.gradients = gradients 17 | self.subroutines = subroutines 18 | self.steps = steps 19 | } 20 | } 21 | 22 | public struct PathRoutine: Sendable { 23 | public var id: String 24 | public var content: [PathSegment] 25 | 26 | public init(id: String, content: [PathSegment]) { 27 | self.id = id 28 | self.content = content 29 | } 30 | } 31 | 32 | public struct Routines: Sendable { 33 | public var drawRoutine: DrawRoutine 34 | public var pathRoutines: [PathRoutine] 35 | 36 | public init(drawRoutine: DrawRoutine, pathRoutines: [PathRoutine] = []) { 37 | self.drawRoutine = drawRoutine 38 | self.pathRoutines = pathRoutines 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demo/Demo/Images/rocket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-15 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup Xcode 17 | uses: maxim-lobanov/setup-xcode@v1 18 | with: 19 | xcode-version: latest-stable 20 | - name: Enable Xcode prebuilts 21 | run: defaults write com.apple.dt.Xcode IDEPackageEnablePrebuilts YES 22 | - name: Build 23 | run: swift build -v --enable-experimental-prebuilts 24 | - name: Run tests 25 | run: SWIFT_DETERMINISTIC_HASHING=1 swift test -v --parallel 26 | - name: Build Demo for macOS 27 | run: xcodebuild -project Demo/Demo.xcodeproj -scheme Demo -configuration Release -destination 'platform=macOS' -skipMacroValidation build 28 | - name: Build Demo for iOS Simulator 29 | run: xcodebuild -project Demo/Demo.xcodeproj -scheme Demo -configuration Release -destination 'generic/platform=iOS Simulator' -skipMacroValidation build 30 | - name: Build CGGenRuntime for iOS 31 | run: xcodebuild -scheme CGGenRuntime -destination 'generic/platform=iOS' -skipMacroValidation build -quiet 32 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/simple_mask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | simple_mask 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Tests/UnitTests/BaseTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | 3 | import CGGenCore 4 | 5 | @Suite struct BaseTests { 6 | @Test func testZip() { 7 | checkZip(zip(Int?.none, Int?.none), nil) 8 | checkZip(zip(42, Int?.none), nil) 9 | checkZip(zip(Int?.none, 42), nil) 10 | checkZip(zip(12, 42), (12, 42)) 11 | } 12 | 13 | @Test func testZipLongest() { 14 | checkZip(zipLongest(42, "42", fillFirst: 0, fillSecond: ""), (42, "42")) 15 | checkZip(zipLongest(nil, "42", fillFirst: 0, fillSecond: ""), (0, "42")) 16 | checkZip(zipLongest(42, nil, fillFirst: 0, fillSecond: ""), (42, "")) 17 | checkZip(zipLongest(nil, nil, fillFirst: 0, fillSecond: ""), nil) 18 | 19 | func failing() -> Int { 20 | Issue.record("Fill function should not be called") 21 | return -1 22 | } 23 | checkZip( 24 | zipLongest(42, 12, fillFirst: failing(), fillSecond: failing()), 25 | (42, 12) 26 | ) 27 | checkZip(zipLongest(nil, 42, fillFirst: 0, fillSecond: failing()), (0, 42)) 28 | checkZip(zipLongest(42, nil, fillFirst: failing(), fillSecond: 0), (42, 0)) 29 | } 30 | } 31 | 32 | private func checkZip( 33 | _ lhs: (T, U)?, 34 | _ rhs: (T, U)? 35 | ) { 36 | #expect(lhs?.0 == rhs?.0) 37 | #expect(lhs?.1 == rhs?.1) 38 | } 39 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/caps_joins.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | caps_joins 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_fill_rule.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 20 | 25 | 26 | -------------------------------------------------------------------------------- /Sources/PDFParse/PDFStream.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | 4 | struct PDFStream { 5 | let raw: CGPDFStreamRef 6 | let dict: [String: PDFObject] 7 | let data: Data 8 | let format: CGPDFDataFormat 9 | 10 | init?(obj: CGPDFObjectRef) { 11 | guard let raw = obj.stream, 12 | let (data, format) = raw.dataAndFormat, 13 | let dict = raw.dictionary 14 | else { return nil } 15 | self.raw = raw 16 | self.data = data 17 | self.dict = PDFObject.processDict(dict) 18 | self.format = format 19 | } 20 | 21 | var rawDict: CGPDFDictionaryRef { 22 | CGPDFStreamGetDictionary(raw)! 23 | } 24 | } 25 | 26 | extension CGPDFObjectRef { 27 | fileprivate var stream: CGPDFStreamRef? { 28 | var streamPtr: CGPDFStreamRef? 29 | guard CGPDFObjectGetValue(self, .stream, &streamPtr) else { 30 | return nil 31 | } 32 | return streamPtr 33 | } 34 | } 35 | 36 | extension CGPDFStreamRef { 37 | fileprivate var dataAndFormat: (Data, CGPDFDataFormat)? { 38 | var format = CGPDFDataFormat.raw 39 | guard let data = CGPDFStreamCopyData(self, &format) as Data? else { 40 | return nil 41 | } 42 | return (data, format) 43 | } 44 | 45 | fileprivate var dictionary: CGPDFDictionaryRef? { 46 | CGPDFStreamGetDictionary(self) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/UnitTests/XMLParserTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | 3 | import CGGenCore 4 | 5 | @Suite struct XMLParserTests { 6 | @Test func testSimpleXML() throws { 7 | #expect(try parse(simpleXML) == .el("note", children: [ 8 | .el("to", children: [.text("Tove")]), 9 | .el("from", children: [.text("Jani")]), 10 | .el("body", children: [.text("Don't forget me this weekend!")]), 11 | ])) 12 | } 13 | 14 | @Test func testSimpleSVG() throws { 15 | #expect(try parse(simpleSVG) == .el( 16 | "svg", attrs: ["width": "50px", "height": "50px"], children: [ 17 | .el("g", attrs: ["stroke": "none", "fill": "none"], children: [ 18 | .el("rect", attrs: ["fill": "#50E3C2", "x": "0", "y": "0"]), 19 | ]), 20 | ] 21 | )) 22 | } 23 | } 24 | 25 | private func parse(_ xml: String) throws -> XML { 26 | try XML.parse(from: xml.data(using: .utf8).unsafelyUnwrapped).get() 27 | } 28 | 29 | private let simpleXML = """ 30 | 31 | Tove 32 | Jani 33 | Don't forget me this weekend! 34 | 35 | """ 36 | 37 | private let simpleSVG = """ 38 | 39 | 40 | 41 | 42 | 43 | 44 | """ 45 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/path_fill_rule_gstate.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/update-webkit-snapshots.yml: -------------------------------------------------------------------------------- 1 | name: Update WebKit Snapshots 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | update-snapshots: 8 | runs-on: macos-15 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Setup Xcode 14 | uses: maxim-lobanov/setup-xcode@v1 15 | with: 16 | xcode-version: latest-stable 17 | 18 | - name: Update snapshots 19 | run: | 20 | swift build --enable-experimental-prebuilts 21 | CGGEN_EXTENDED_TESTS=1 SNAPSHOT_TESTING_RECORD=failed swift test || true 22 | 23 | - name: Create PR 24 | run: | 25 | if ! git diff --quiet; then 26 | git config user.name "github-actions[bot]" 27 | git config user.email "github-actions[bot]@users.noreply.github.com" 28 | 29 | BRANCH="update-snapshots-$(date +%Y%m%d-%H%M%S)" 30 | git checkout -b "$BRANCH" 31 | git add "Tests/RegressionTests/__Snapshots__/" 32 | git commit -m "Update WebKit reference snapshots" 33 | git push origin "$BRANCH" 34 | 35 | gh pr create \ 36 | --title "Update WebKit reference snapshots" \ 37 | --body "Updates WebKit reference snapshots to match current rendering output." \ 38 | --base main 39 | else 40 | echo "No changes needed" 41 | fi 42 | env: 43 | GH_TOKEN: ${{ github.token }} 44 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/underlying_object_with_tiny_alpha.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | underlying_object_with_tiny_alpha 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/RegressionTests/pdf_samples/white_cross_scn_operator.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /Pages 2 0 R 5 | /Type /Catalog 6 | >> 7 | endobj 8 | 9 | 2 0 obj 10 | << /Kids [ 3 0 R ] 11 | /Count 1 12 | /Type /Pages 13 | >> 14 | endobj 15 | 16 | 3 0 obj 17 | << /MediaBox [ 0.000000 0.000000 18.000000 18.000000 ] 18 | /Resources 6 0 R 19 | /Contents 4 0 R 20 | /Parent 2 0 R 21 | /Type /Page 22 | >> 23 | endobj 24 | 25 | 4 0 obj 26 | << /Length 5 0 R >> 27 | stream 28 | /DeviceRGB CS 29 | /DeviceRGB cs 30 | q 31 | 1.000000 0.000000 -0.000000 1.000000 3.000000 3.000000 cm 32 | 7.050040 6.000000 m 33 | 12.000000 1.050044 l 34 | 10.949960 0.000000 l 35 | 6.000000 4.949956 l 36 | 1.050044 0.000000 l 37 | 0.000000 1.050044 l 38 | 4.949956 6.000000 l 39 | 0.000000 10.949961 l 40 | 1.050044 12.000000 l 41 | 6.000000 7.050040 l 42 | 10.949960 12.000000 l 43 | 12.000000 10.949961 l 44 | 7.050040 6.000000 l 45 | h 46 | 1.000000 1.000000 1.000000 scn 47 | f 48 | n 49 | Q 50 | 51 | endstream 52 | endobj 53 | 54 | 5 0 obj 395 endobj 55 | 56 | 6 0 obj 57 | << >> 58 | endobj 59 | 60 | xref 61 | 0 7 62 | 0000000000 65535 f 63 | 0000000010 00000 n 64 | 0000000069 00000 n 65 | 0000000143 00000 n 66 | 0000000300 00000 n 67 | 0000000751 00000 n 68 | 0000000771 00000 n 69 | trailer 70 | << /ID [ (some) 71 | (id) 72 | ] 73 | /Root 1 0 R 74 | /Size 7 75 | >> 76 | startxref 77 | 795 78 | %%EOF -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_fill_stroke_combinations.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Podspecs/README.md: -------------------------------------------------------------------------------- 1 | # CocoaPods Support 2 | 3 | CocoaPods support is provided **only for the runtime library** (`CGGenRTSupport`). 4 | 5 | The cggen CLI tool is not available via CocoaPods. Use Swift Package Manager for the CLI. 6 | 7 | ## Usage 8 | 9 | Add to your Podfile: 10 | 11 | ```ruby 12 | platform :ios, '13.0' 13 | 14 | CGGEN = 'https://raw.githubusercontent.com/yandex/cggen/main/Podspecs' 15 | 16 | target 'MyApp' do 17 | use_frameworks! 18 | 19 | pod 'CGGenBytecode', :podspec => "#{CGGEN}/CGGenBytecode.podspec" 20 | pod 'CGGenBytecodeDecoding', :podspec => "#{CGGEN}/CGGenBytecodeDecoding.podspec" 21 | pod 'CGGenRTSupport', :podspec => "#{CGGEN}/CGGenRTSupport.podspec" 22 | end 23 | ``` 24 | 25 | For multiple targets, use a function: 26 | 27 | ```ruby 28 | CGGEN = 'https://raw.githubusercontent.com/yandex/cggen/main/Podspecs' 29 | 30 | def cggen_runtime 31 | pod 'CGGenBytecode', :podspec => "#{CGGEN}/CGGenBytecode.podspec" 32 | pod 'CGGenBytecodeDecoding', :podspec => "#{CGGEN}/CGGenBytecodeDecoding.podspec" 33 | pod 'CGGenRTSupport', :podspec => "#{CGGEN}/CGGenRTSupport.podspec" 34 | end 35 | 36 | target 'MyApp' do 37 | use_frameworks! 38 | cggen_runtime 39 | end 40 | 41 | target 'MyAppTests' do 42 | use_frameworks! 43 | cggen_runtime 44 | end 45 | ``` 46 | 47 | Then run: 48 | 49 | ```bash 50 | pod install 51 | ``` 52 | 53 | ## Note 54 | 55 | CocoaPods trunk is scheduled to become read-only in December 2026. Consider migrating to Swift Package Manager. 56 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | shapes 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Sources/CGGenCore/Logger.swift: -------------------------------------------------------------------------------- 1 | import os.log 2 | 3 | public struct Logger { 4 | public nonisolated(unsafe) static var shared = Logger() 5 | private var level: Bool? 6 | public mutating func setLevel(level: Bool) { 7 | self.level = level 8 | } 9 | 10 | func log(_ s: String) { 11 | guard let level else { fatalError("log level must be set") } 12 | if level { 13 | print(s) 14 | } 15 | } 16 | } 17 | 18 | public func log(_ s: String) { 19 | Logger.shared.log(s) 20 | } 21 | 22 | extension OSLog { 23 | @usableFromInline 24 | class Guard { 25 | var expectDealloc = false 26 | var reentranceGuard = true 27 | 28 | @usableFromInline 29 | init() {} 30 | 31 | @usableFromInline 32 | func enter() { 33 | precondition(reentranceGuard) 34 | reentranceGuard = false 35 | expectDealloc = true 36 | } 37 | 38 | deinit { 39 | precondition(expectDealloc) 40 | } 41 | } 42 | 43 | @inlinable 44 | public func signpost(_ desc: StaticString) -> () -> Void { 45 | let g = Guard() 46 | os_signpost(.begin, log: self, name: desc) 47 | return { 48 | os_signpost(.end, log: self, name: desc) 49 | g.enter() 50 | } 51 | } 52 | 53 | @inlinable 54 | public func signpostRegion( 55 | _ desc: StaticString, 56 | _ region: () throws -> T 57 | ) rethrows -> T { 58 | os_signpost(.begin, log: self, name: desc) 59 | defer { 60 | os_signpost(.end, log: self, name: desc) 61 | } 62 | return try region() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_determinism_test.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_with_mask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gradient_with_mask 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/gradient_opacity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | gradient 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Tests/RegressionTests/BCCompilationTests.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import CoreGraphics 3 | import Foundation 4 | import Testing 5 | 6 | import CGGenCLI 7 | 8 | @Suite struct BCCompilationTests { 9 | @Test func compilation() throws { 10 | let variousFilenamesDir = 11 | getCurrentFilePath().appendingPathComponent("various_filenames") 12 | let files = [ 13 | "Capital letter.svg", 14 | "dash-dash.svg", 15 | "under_score.svg", 16 | "white space.svg", 17 | ] 18 | 19 | let fm = FileManager.default 20 | 21 | let tmpdir = try fm.url( 22 | for: .itemReplacementDirectory, 23 | in: .userDomainMask, 24 | appropriateFor: fm.homeDirectoryForCurrentUser, 25 | create: true 26 | ) 27 | defer { 28 | do { 29 | try fm.removeItem(at: tmpdir) 30 | } catch { 31 | fatalError("Unable to clean up dir: \(tmpdir), error: \(error)") 32 | } 33 | } 34 | 35 | let header = tmpdir.appendingPathComponent("gen.h").path 36 | let impl = tmpdir.appendingPathComponent("gen.m") 37 | 38 | try runCggen( 39 | with: .init( 40 | objcHeader: header, 41 | objcPrefix: "Tests", 42 | objcImpl: impl.path, 43 | objcHeaderImportPath: header, 44 | generationStyle: .plain, 45 | cggenSupportHeaderPath: nil, 46 | module: nil, 47 | verbose: false, 48 | files: files 49 | .map { variousFilenamesDir.appendingPathComponent($0).path }, 50 | swiftOutput: nil 51 | ) 52 | ) 53 | 54 | try clang( 55 | out: nil, 56 | files: [impl], 57 | syntaxOnly: true, 58 | frameworks: [] 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/gradientDeterminismGeneration.1.swift: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | import CoreGraphics 4 | @_spi(Generator) import CGGenRTSupport 5 | 6 | typealias Drawing = CGGenRTSupport.Drawing 7 | 8 | extension Drawing { 9 | static let gradientDeterminismTest = Drawing( 10 | width: 50.0, 11 | height: 50.0, 12 | bytecodeArray: mergedBytecodes, 13 | decompressedSize: 408, 14 | startIndex: 0, 15 | endIndex: 407 16 | ) 17 | } 18 | 19 | private let mergedBytecodes: [UInt8] = [ 20 | 0x62, 0x76, 0x78, 0x6E, 0x98, 0x01, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00, 21 | 0xE5, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x98, 0x01, 0x02, 0x00, 22 | 0x48, 0x05, 0xFF, 0x98, 0x04, 0x80, 0x3F, 0x48, 0x07, 0xFF, 0x08, 0x1A, 23 | 0x28, 0x1E, 0x9E, 0x00, 0x80, 0xF3, 0x90, 0x0B, 0xFF, 0xFF, 0x68, 0x1E, 24 | 0x03, 0xF4, 0x50, 0x31, 0xFF, 0x10, 0x1E, 0x18, 0x3C, 0x6E, 0x04, 0xF3, 25 | 0x48, 0x3D, 0x80, 0x20, 0x1E, 0x8E, 0xA5, 0x00, 0x6E, 0x05, 0xF3, 0x10, 26 | 0x01, 0x28, 0x5A, 0x20, 0x47, 0xC8, 0x09, 0x00, 0x00, 0x1A, 0xF4, 0x18, 27 | 0x01, 0x98, 0x08, 0x80, 0xBF, 0xE6, 0x48, 0x42, 0x00, 0x26, 0x2A, 0x05, 28 | 0x18, 0x0C, 0x20, 0x01, 0x88, 0x06, 0x20, 0x41, 0x00, 0x8E, 0x98, 0x01, 29 | 0x06, 0x00, 0xF3, 0x08, 0x14, 0xE4, 0x48, 0x42, 0x18, 0x01, 0x00, 0x2D, 30 | 0x68, 0x11, 0x01, 0xF1, 0x08, 0x01, 0x68, 0x2D, 0xA0, 0xF3, 0x28, 0x14, 31 | 0x38, 0x2D, 0xF1, 0x18, 0x94, 0x60, 0x19, 0xA0, 0x68, 0x2D, 0xF0, 0xF3, 32 | 0x6E, 0xA0, 0xFA, 0x56, 0x04, 0x68, 0x19, 0xF0, 0x48, 0x06, 0x42, 0x18, 33 | 0x2D, 0x6E, 0xF0, 0xFA, 0x58, 0x11, 0x03, 0x10, 0x25, 0x08, 0x15, 0x08, 34 | 0x06, 0x20, 0x87, 0x6E, 0x42, 0xF4, 0xE2, 0x18, 0x01, 0x06, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x76, 0x78, 0x24 36 | ] 37 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "b007b967b3a5b71ea17963ef3e3a32d5b21f47cecfd6d52b999336029480419d", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-argument-parser", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/apple/swift-argument-parser.git", 8 | "state" : { 9 | "revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3", 10 | "version" : "1.6.1" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-case-paths", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/pointfreeco/swift-case-paths", 17 | "state" : { 18 | "revision" : "9810c8d6c2914de251e072312f01d3bf80071852", 19 | "version" : "1.7.1" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-parsing", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/pointfreeco/swift-parsing", 26 | "state" : { 27 | "revision" : "3432cb81164dd3d69a75d0d63205be5fbae2c34b", 28 | "version" : "0.14.1" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-syntax", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/swiftlang/swift-syntax", 35 | "state" : { 36 | "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2", 37 | "version" : "601.0.1" 38 | } 39 | }, 40 | { 41 | "identity" : "xctest-dynamic-overlay", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 44 | "state" : { 45 | "revision" : "b2ed9eabefe56202ee4939dd9fc46b6241c88317", 46 | "version" : "1.6.1" 47 | } 48 | } 49 | ], 50 | "version" : 3 51 | } 52 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | }, 30 | { 31 | "idiom" : "mac", 32 | "scale" : "1x", 33 | "size" : "16x16" 34 | }, 35 | { 36 | "idiom" : "mac", 37 | "scale" : "2x", 38 | "size" : "16x16" 39 | }, 40 | { 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "32x32" 44 | }, 45 | { 46 | "idiom" : "mac", 47 | "scale" : "2x", 48 | "size" : "32x32" 49 | }, 50 | { 51 | "idiom" : "mac", 52 | "scale" : "1x", 53 | "size" : "128x128" 54 | }, 55 | { 56 | "idiom" : "mac", 57 | "scale" : "2x", 58 | "size" : "128x128" 59 | }, 60 | { 61 | "idiom" : "mac", 62 | "scale" : "1x", 63 | "size" : "256x256" 64 | }, 65 | { 66 | "idiom" : "mac", 67 | "scale" : "2x", 68 | "size" : "256x256" 69 | }, 70 | { 71 | "idiom" : "mac", 72 | "scale" : "1x", 73 | "size" : "512x512" 74 | }, 75 | { 76 | "idiom" : "mac", 77 | "scale" : "2x", 78 | "size" : "512x512" 79 | } 80 | ], 81 | "info" : { 82 | "author" : "xcode", 83 | "version" : 1 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/SVGParse/SVGShapeParser.swift: -------------------------------------------------------------------------------- 1 | @preconcurrency import Parsing 2 | 3 | enum SVGShapeParser { 4 | typealias Attribute = SVGAttributeParser 5 | typealias AttributeGroup = SVGAttributeGroupParser 6 | 7 | struct ShapeParser: Parser, Sendable 8 | where DataParser.Input == [String: String], 9 | DataParser.Output: Equatable & Sendable { 10 | typealias Input = [String: String] 11 | typealias Output = SVG.ShapeElement 12 | 13 | let dataParser: DataParser 14 | 15 | init(_ dataParser: DataParser) { 16 | self.dataParser = dataParser 17 | } 18 | 19 | var body: some Parser { 20 | Parse(SVG.ShapeElement.init) { 21 | AttributeGroup.core 22 | AttributeGroup.presentation 23 | Attribute.Transform(.transform) 24 | dataParser 25 | } 26 | } 27 | } 28 | 29 | static let rect = ShapeParser( 30 | Parse(SVG.RectData.init) { 31 | AttributeGroup.x 32 | AttributeGroup.y 33 | Attribute.Len(.rx) 34 | Attribute.Len(.ry) 35 | AttributeGroup.width 36 | AttributeGroup.height 37 | } 38 | ) 39 | 40 | static let polygon = ShapeParser( 41 | Attribute.ListOfPoints(.points).map(SVG.PolygonData.init) 42 | ) 43 | 44 | static let circle = ShapeParser( 45 | Parse(SVG.CircleData.init) { 46 | Attribute.Coord(.cx) 47 | Attribute.Coord(.cy) 48 | Attribute.Coord(.r) 49 | } 50 | ) 51 | 52 | static let ellipse = ShapeParser( 53 | Parse(SVG.EllipseData.init) { 54 | Attribute.Coord(.cx) 55 | Attribute.Coord(.cy) 56 | Attribute.Len(.rx) 57 | Attribute.Len(.ry) 58 | } 59 | ) 60 | 61 | static let path = ShapeParser( 62 | Parse(SVG.PathData.init) { 63 | Attribute.PathData(.d) 64 | Attribute.Num(.pathLength) 65 | } 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /Demo/Demo/DemoApp.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import SwiftUI 3 | 4 | enum DemoTab: String, CaseIterable, ExpressibleByArgument { 5 | case swiftui 6 | case appkit 7 | case playground 8 | } 9 | 10 | struct CLIArgs: ParsableArguments { 11 | @Option var tab: DemoTab = .swiftui 12 | 13 | // Capture all unrecognized arguments to prevent errors 14 | @Argument(parsing: .allUnrecognized) var unrecognized: [String] = [] 15 | } 16 | 17 | @main 18 | struct DemoApp: App { 19 | @State private var selectedTab: DemoTab 20 | 21 | init() { 22 | let args = CLIArgs.parseOrExit() 23 | _selectedTab = State(initialValue: args.tab) 24 | } 25 | 26 | var body: some Scene { 27 | WindowGroup { 28 | ContentView(selectedTab: $selectedTab) 29 | } 30 | } 31 | } 32 | 33 | struct ContentView: View { 34 | @Binding var selectedTab: DemoTab 35 | 36 | var body: some View { 37 | TabView(selection: $selectedTab) { 38 | NavigationStack { 39 | SwiftUIDemo() 40 | } 41 | .tabItem { 42 | Label("SwiftUI", systemImage: "swift") 43 | } 44 | .tag(DemoTab.swiftui) 45 | 46 | #if canImport(UIKit) 47 | NavigationStack { 48 | UIKitDemo() 49 | } 50 | .tabItem { 51 | Label("UIKit", systemImage: "uiwindow.split.2x1") 52 | } 53 | .tag(DemoTab.appkit) 54 | #elseif canImport(AppKit) 55 | NavigationStack { 56 | AppKitDemo() 57 | } 58 | .tabItem { 59 | Label("AppKit", systemImage: "macwindow") 60 | } 61 | .tag(DemoTab.appkit) 62 | #endif 63 | 64 | NavigationStack { 65 | PlaygroundView() 66 | } 67 | .tabItem { 68 | Label("Playground", systemImage: "paintbrush.pointed") 69 | } 70 | .tag(DemoTab.playground) 71 | } 72 | } 73 | } 74 | 75 | #Preview { 76 | ContentView(selectedTab: .constant(.swiftui)) 77 | } 78 | -------------------------------------------------------------------------------- /Sources/cggen/CGGen.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | 4 | import CGGenCLI 5 | import CGGenCore 6 | 7 | extension GenerationStyle: ExpressibleByArgument {} 8 | 9 | struct CGGen: ParsableCommand { 10 | @Option var objcHeader: String? 11 | @Option var objcPrefix = "" 12 | @Option var objcImpl: String? 13 | @Option var bytecodeFilePrefix: String? 14 | @Option var objcHeaderImportPath: String? 15 | @Option(help: "Interface generation style, swift-friendly or plain") 16 | var generationStyle: GenerationStyle = .plain 17 | @Option var cggenSupportHeaderPath: String? 18 | @Option var moduleName = "" 19 | @Option var swiftOutput: String? 20 | @Flag var verbose = false 21 | @Argument var files: [String] 22 | 23 | static let configuration = CommandConfiguration( 24 | commandName: "cggen", 25 | abstract: "Tool for generating Core Graphics code from SVG and PDF files", 26 | version: "1.0.0" 27 | ) 28 | 29 | func run() throws { 30 | try runCggen(with: Args( 31 | objcHeader: objcHeader, 32 | objcPrefix: objcPrefix, 33 | objcImpl: objcImpl, 34 | objcHeaderImportPath: objcHeaderImportPath, 35 | generationStyle: generationStyle, 36 | cggenSupportHeaderPath: cggenSupportHeaderPath, 37 | module: moduleName, 38 | verbose: verbose, 39 | files: files, 40 | swiftOutput: swiftOutput 41 | )) 42 | } 43 | } 44 | 45 | @main 46 | struct Main { 47 | static func main() { 48 | SignalHandling.intercepting(.bus, .segmentationFault) { 49 | CGGen.main() 50 | } onSignal: { signal in 51 | print("\(signal.name) received!") 52 | print("Working directory: \(FileManager.default.currentDirectoryPath)") 53 | print("Command line: \(CommandLine.arguments.joined(separator: " "))") 54 | print("\nStack trace:") 55 | Thread.callStackSymbols.forEach { print($0) } 56 | fflush(stdout) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/mixedGeneration.1.swift: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | import CoreGraphics 4 | @_spi(Generator) import CGGenRTSupport 5 | 6 | typealias Drawing = CGGenRTSupport.Drawing 7 | 8 | extension Drawing { 9 | static let pathsAndImages = Drawing( 10 | width: 50.0, 11 | height: 50.0, 12 | bytecodeArray: mergedBytecodes, 13 | decompressedSize: 201, 14 | startIndex: 0, 15 | endIndex: 80 16 | ) 17 | } 18 | 19 | 20 | // MARK: - Paths 21 | 22 | extension Drawing.Path { 23 | static let diamond = Drawing.Path( 24 | bytecodeArray: mergedBytecodes, 25 | decompressedSize: 201, 26 | startIndex: 81, 27 | endIndex: 117 28 | ) 29 | static let hexagon = Drawing.Path( 30 | bytecodeArray: mergedBytecodes, 31 | decompressedSize: 201, 32 | startIndex: 118, 33 | endIndex: 172 34 | ) 35 | static let triangle = Drawing.Path( 36 | bytecodeArray: mergedBytecodes, 37 | decompressedSize: 201, 38 | startIndex: 173, 39 | endIndex: 200 40 | ) 41 | } 42 | 43 | private let mergedBytecodes: [UInt8] = [ 44 | 0x62, 0x76, 0x78, 0x6E, 0xC9, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 45 | 0x60, 0x01, 0x00, 0xE6, 0x1A, 0x00, 0x00, 0x80, 0x3F, 0x00, 0xF9, 0x98, 46 | 0x08, 0x80, 0xBF, 0xEC, 0x48, 0x42, 0x00, 0x24, 0xFF, 0x00, 0x00, 0x06, 47 | 0x00, 0x00, 0xA0, 0x40, 0x18, 0x04, 0x8E, 0x70, 0x41, 0xEC, 0x18, 0x01, 48 | 0x00, 0x24, 0x00, 0x00, 0xFF, 0x17, 0x00, 0x00, 0xDC, 0x41, 0x38, 0x18, 49 | 0xF5, 0xC8, 0x13, 0x00, 0x00, 0xC8, 0xF1, 0xE5, 0x03, 0x00, 0x00, 0x34, 50 | 0x42, 0x08, 0x0D, 0x58, 0x12, 0x03, 0xC8, 0x2A, 0x34, 0x42, 0x03, 0xF2, 51 | 0xE4, 0xC8, 0x41, 0x07, 0x00, 0x18, 0x30, 0x48, 0x1C, 0x20, 0x98, 0x09, 52 | 0x0C, 0x42, 0xF1, 0x30, 0x2E, 0x10, 0x12, 0x08, 0x2E, 0x10, 0x24, 0x38, 53 | 0x37, 0xF4, 0x38, 0x5C, 0xF3, 0x30, 0x1C, 0xE3, 0x20, 0x42, 0x07, 0x06, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x76, 0x78, 0x24 55 | ] 56 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pluginDemoGeneration.1.swift: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | import CoreGraphics 4 | @_spi(Generator) import CGGenRTSupport 5 | 6 | typealias Drawing = CGGenRTSupport.Drawing 7 | 8 | extension Drawing { 9 | static let circle = Drawing( 10 | width: 50.0, 11 | height: 50.0, 12 | bytecodeArray: mergedBytecodes, 13 | decompressedSize: 275, 14 | startIndex: 0, 15 | endIndex: 65 16 | ) 17 | static let square = Drawing( 18 | width: 40.0, 19 | height: 40.0, 20 | bytecodeArray: mergedBytecodes, 21 | decompressedSize: 275, 22 | startIndex: 66, 23 | endIndex: 139 24 | ) 25 | static let star = Drawing( 26 | width: 60.0, 27 | height: 60.0, 28 | bytecodeArray: mergedBytecodes, 29 | decompressedSize: 275, 30 | startIndex: 140, 31 | endIndex: 274 32 | ) 33 | } 34 | 35 | private let mergedBytecodes: [UInt8] = [ 36 | 0x62, 0x76, 0x78, 0x6E, 0x13, 0x01, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00, 37 | 0x60, 0x01, 0x00, 0xE6, 0x1A, 0x00, 0x00, 0x80, 0x3F, 0x00, 0xF9, 0x98, 38 | 0x08, 0x80, 0xBF, 0xE4, 0x48, 0x42, 0x00, 0x1C, 0xF3, 0xEE, 0x40, 0x24, 39 | 0x34, 0x98, 0xDB, 0x21, 0x2C, 0x3E, 0x50, 0x17, 0x00, 0x00, 0xA0, 0x40, 40 | 0x18, 0x04, 0x8E, 0x20, 0x42, 0xC8, 0x01, 0x18, 0x01, 0x00, 0xF3, 0x38, 41 | 0x42, 0xFD, 0x6E, 0x20, 0xE8, 0xE7, 0x4C, 0x3C, 0x21, 0xC0, 0x39, 0x2B, 42 | 0x07, 0xFA, 0x98, 0x04, 0xF0, 0x41, 0x18, 0x10, 0x38, 0x4A, 0xF0, 0x07, 43 | 0x6E, 0x70, 0xE9, 0xF1, 0xC4, 0x0F, 0x21, 0xE6, 0x7E, 0x22, 0x0B, 0x0A, 44 | 0x10, 0x17, 0x28, 0x42, 0x40, 0x90, 0x14, 0x40, 0x0C, 0xB0, 0x60, 0x08, 45 | 0x5C, 0x46, 0x28, 0x40, 0x04, 0x0C, 0x40, 0x08, 0x3C, 0x98, 0x28, 0x50, 46 | 0x42, 0x08, 0x14, 0x98, 0x10, 0x50, 0x41, 0x98, 0x20, 0x90, 0x41, 0x08, 47 | 0x3C, 0x08, 0x30, 0xE9, 0xB8, 0x41, 0x00, 0x00, 0xB0, 0x41, 0x09, 0x18, 48 | 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x76, 0x78, 49 | 0x24 50 | ] 51 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/gradientDeterminismGeneration.3.m: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | void runMergedBytecode(CGContextRef context, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 4 | void runPathBytecode(CGMutablePathRef path, const uint8_t* arr, int len); 5 | void runMergedPathBytecode(CGMutablePathRef path, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 6 | 7 | static const uint8_t mergedBytecodes[]; 8 | 9 | void grad_determDrawGradientDeterminismTestImageInContext(CGContextRef context) { 10 | runMergedBytecode(context, mergedBytecodes, 408, 189, 0, 407); 11 | } 12 | 13 | static const uint8_t mergedBytecodes[] = { 14 | 0x62, 0x76, 0x78, 0x6E, 0x98, 0x01, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00, 15 | 0xE5, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x98, 0x01, 0x02, 0x00, 16 | 0x48, 0x05, 0xFF, 0x98, 0x04, 0x80, 0x3F, 0x48, 0x07, 0xFF, 0x08, 0x1A, 17 | 0x28, 0x1E, 0x9E, 0x00, 0x80, 0xF3, 0x90, 0x0B, 0xFF, 0xFF, 0x68, 0x1E, 18 | 0x03, 0xF4, 0x50, 0x31, 0xFF, 0x10, 0x1E, 0x18, 0x3C, 0x6E, 0x04, 0xF3, 19 | 0x48, 0x3D, 0x80, 0x20, 0x1E, 0x8E, 0xA5, 0x00, 0x6E, 0x05, 0xF3, 0x10, 20 | 0x01, 0x28, 0x5A, 0x20, 0x47, 0xC8, 0x09, 0x00, 0x00, 0x1A, 0xF4, 0x18, 21 | 0x01, 0x98, 0x08, 0x80, 0xBF, 0xE6, 0x48, 0x42, 0x00, 0x26, 0x2A, 0x05, 22 | 0x18, 0x0C, 0x20, 0x01, 0x88, 0x06, 0x20, 0x41, 0x00, 0x8E, 0x98, 0x01, 23 | 0x06, 0x00, 0xF3, 0x08, 0x14, 0xE4, 0x48, 0x42, 0x18, 0x01, 0x00, 0x2D, 24 | 0x68, 0x11, 0x01, 0xF1, 0x08, 0x01, 0x68, 0x2D, 0xA0, 0xF3, 0x28, 0x14, 25 | 0x38, 0x2D, 0xF1, 0x18, 0x94, 0x60, 0x19, 0xA0, 0x68, 0x2D, 0xF0, 0xF3, 26 | 0x6E, 0xA0, 0xFA, 0x56, 0x04, 0x68, 0x19, 0xF0, 0x48, 0x06, 0x42, 0x18, 27 | 0x2D, 0x6E, 0xF0, 0xFA, 0x58, 0x11, 0x03, 0x10, 0x25, 0x08, 0x15, 0x08, 28 | 0x06, 0x20, 0x87, 0x6E, 0x42, 0xF4, 0xE2, 0x18, 0x01, 0x06, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x76, 0x78, 0x24 30 | }; 31 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/mixedGeneration.3.m: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | void runMergedBytecode(CGContextRef context, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 4 | void runPathBytecode(CGMutablePathRef path, const uint8_t* arr, int len); 5 | void runMergedPathBytecode(CGMutablePathRef path, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 6 | 7 | static const uint8_t mergedBytecodes[]; 8 | 9 | void mixedDrawPathsAndImagesImageInContext(CGContextRef context) { 10 | runMergedBytecode(context, mergedBytecodes, 201, 131, 0, 80); 11 | } 12 | 13 | void mixedDiamondPath(CGMutablePathRef path) { 14 | runMergedPathBytecode(path, mergedBytecodes, 201, 131, 81, 117); 15 | } 16 | 17 | void mixedHexagonPath(CGMutablePathRef path) { 18 | runMergedPathBytecode(path, mergedBytecodes, 201, 131, 118, 172); 19 | } 20 | 21 | void mixedTrianglePath(CGMutablePathRef path) { 22 | runMergedPathBytecode(path, mergedBytecodes, 201, 131, 173, 200); 23 | } 24 | 25 | static const uint8_t mergedBytecodes[] = { 26 | 0x62, 0x76, 0x78, 0x6E, 0xC9, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 27 | 0x60, 0x01, 0x00, 0xE6, 0x1A, 0x00, 0x00, 0x80, 0x3F, 0x00, 0xF9, 0x98, 28 | 0x08, 0x80, 0xBF, 0xEC, 0x48, 0x42, 0x00, 0x24, 0xFF, 0x00, 0x00, 0x06, 29 | 0x00, 0x00, 0xA0, 0x40, 0x18, 0x04, 0x8E, 0x70, 0x41, 0xEC, 0x18, 0x01, 30 | 0x00, 0x24, 0x00, 0x00, 0xFF, 0x17, 0x00, 0x00, 0xDC, 0x41, 0x38, 0x18, 31 | 0xF5, 0xC8, 0x13, 0x00, 0x00, 0xC8, 0xF1, 0xE5, 0x03, 0x00, 0x00, 0x34, 32 | 0x42, 0x08, 0x0D, 0x58, 0x12, 0x03, 0xC8, 0x2A, 0x34, 0x42, 0x03, 0xF2, 33 | 0xE4, 0xC8, 0x41, 0x07, 0x00, 0x18, 0x30, 0x48, 0x1C, 0x20, 0x98, 0x09, 34 | 0x0C, 0x42, 0xF1, 0x30, 0x2E, 0x10, 0x12, 0x08, 0x2E, 0x10, 0x24, 0x38, 35 | 0x37, 0xF4, 0x38, 0x5C, 0xF3, 0x30, 0x1C, 0xE3, 0x20, 0x42, 0x07, 0x06, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x76, 0x78, 0x24 37 | }; 38 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | # Notice to external contributors 2 | 3 | 4 | ## General info 5 | 6 | Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Yandex Contributor License Agreement (the “**CLA**”). The current version of the CLA can be found here: 7 | 1) https://yandex.ru/legal/cla/?lang=en (in English) and 8 | 2) https://yandex.ru/legal/cla/?lang=ru (in Russian). 9 | 10 | By adopting the CLA, you state the following: 11 | 12 | * You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA, 13 | * You have read the terms and conditions of the CLA and agree with them in full, 14 | * You are legally able to provide and license your contributions as stated, 15 | * We may use your contributions for our open source projects and for any other our project too, 16 | * We rely on your assurances concerning the rights of third parties in relation to your contributions. 17 | 18 | If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you have already read and adopt our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA. 19 | 20 | ## Provide contributions 21 | 22 | If you have already adopted terms and conditions of the CLA, you are able to provide your contributions. When you submit your pull request, please add the following information into it: 23 | 24 | ``` 25 | I hereby agree to the terms of the CLA available at: [link]. 26 | ``` 27 | 28 | Replace the bracketed text as follows: 29 | * [link] is the link to the current version of the CLA: https://yandex.ru/legal/cla/?lang=en (in English) or https://yandex.ru/legal/cla/?lang=ru (in Russian). 30 | 31 | It is enough to provide us such notification once. 32 | 33 | ## Other questions 34 | 35 | If you have any questions, please mail us at opensource@yandex-team.ru. 36 | -------------------------------------------------------------------------------- /Tests/CGGenTests/SVGRendererTests.swift: -------------------------------------------------------------------------------- 1 | import CGGenRuntime 2 | import CoreGraphics 3 | import Foundation 4 | import Testing 5 | 6 | @Suite struct SVGRendererTests { 7 | func render( 8 | _ svg: String, 9 | size: CGSize = .init(width: 100, height: 100) 10 | ) throws -> CGImage { 11 | try SVGRenderer.createCGImage(from: Data(svg.utf8), size: size) 12 | } 13 | 14 | @Test func emptyData() { 15 | #expect(throws: Error.self) { try render("") } 16 | } 17 | 18 | @Test func invalidSVG() { 19 | #expect(throws: Error.self) { try render("bad") } 20 | } 21 | 22 | @Test func invalidSize() { 23 | #expect(throws: SVGRenderer.Error.invalidSize) { 24 | try render(#""#, size: .zero) 25 | } 26 | } 27 | 28 | @Test func fillRect() throws { 29 | let svg = #""" 30 | 31 | 32 | 33 | """# 34 | 35 | let image = try render(svg, size: CGSize(width: 50, height: 50)) 36 | #expect(!isImageEmpty(image)) 37 | } 38 | 39 | func isImageEmpty(_ image: CGImage) -> Bool { 40 | // Check if the image has any non-white pixels 41 | let width = image.width 42 | let height = image.height 43 | let bytesPerPixel = 4 44 | let bytesPerRow = width * bytesPerPixel 45 | let totalBytes = height * bytesPerRow 46 | 47 | guard let data = image.dataProvider?.data, 48 | let bytes = CFDataGetBytePtr(data) else { 49 | return true 50 | } 51 | 52 | // Check if all pixels are white (255, 255, 255) 53 | for i in stride(from: 0, to: totalBytes, by: bytesPerPixel) { 54 | let r = bytes[i] 55 | let g = bytes[i + 1] 56 | let b = bytes[i + 2] 57 | 58 | // If we find any non-white pixel, the image is not empty 59 | if r != 255 || g != 255 || b != 255 { 60 | return false 61 | } 62 | } 63 | 64 | return true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pluginDemoGeneration.3.m: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | void runMergedBytecode(CGContextRef context, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 4 | void runPathBytecode(CGMutablePathRef path, const uint8_t* arr, int len); 5 | void runMergedPathBytecode(CGMutablePathRef path, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 6 | 7 | static const uint8_t mergedBytecodes[]; 8 | 9 | void plugin_demoDrawCircleImageInContext(CGContextRef context) { 10 | runMergedBytecode(context, mergedBytecodes, 275, 157, 0, 65); 11 | } 12 | 13 | void plugin_demoDrawSquareImageInContext(CGContextRef context) { 14 | runMergedBytecode(context, mergedBytecodes, 275, 157, 66, 139); 15 | } 16 | 17 | void plugin_demoDrawStarImageInContext(CGContextRef context) { 18 | runMergedBytecode(context, mergedBytecodes, 275, 157, 140, 274); 19 | } 20 | 21 | static const uint8_t mergedBytecodes[] = { 22 | 0x62, 0x76, 0x78, 0x6E, 0x13, 0x01, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00, 23 | 0x60, 0x01, 0x00, 0xE6, 0x1A, 0x00, 0x00, 0x80, 0x3F, 0x00, 0xF9, 0x98, 24 | 0x08, 0x80, 0xBF, 0xE4, 0x48, 0x42, 0x00, 0x1C, 0xF3, 0xEE, 0x40, 0x24, 25 | 0x34, 0x98, 0xDB, 0x21, 0x2C, 0x3E, 0x50, 0x17, 0x00, 0x00, 0xA0, 0x40, 26 | 0x18, 0x04, 0x8E, 0x20, 0x42, 0xC8, 0x01, 0x18, 0x01, 0x00, 0xF3, 0x38, 27 | 0x42, 0xFD, 0x6E, 0x20, 0xE8, 0xE7, 0x4C, 0x3C, 0x21, 0xC0, 0x39, 0x2B, 28 | 0x07, 0xFA, 0x98, 0x04, 0xF0, 0x41, 0x18, 0x10, 0x38, 0x4A, 0xF0, 0x07, 29 | 0x6E, 0x70, 0xE9, 0xF1, 0xC4, 0x0F, 0x21, 0xE6, 0x7E, 0x22, 0x0B, 0x0A, 30 | 0x10, 0x17, 0x28, 0x42, 0x40, 0x90, 0x14, 0x40, 0x0C, 0xB0, 0x60, 0x08, 31 | 0x5C, 0x46, 0x28, 0x40, 0x04, 0x0C, 0x40, 0x08, 0x3C, 0x98, 0x28, 0x50, 32 | 0x42, 0x08, 0x14, 0x98, 0x10, 0x50, 0x41, 0x98, 0x20, 0x90, 0x41, 0x08, 33 | 0x3C, 0x08, 0x30, 0xE9, 0xB8, 0x41, 0x00, 0x00, 0xB0, 0x41, 0x09, 0x18, 34 | 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x76, 0x78, 35 | 0x24 36 | }; 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to cggen will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.0.0] - 2025-06-19 9 | 10 | ### Added 11 | - Runtime SVG rendering support via `CGGenRuntime` module (#88) 12 | - Swift Package Manager plugin for automatic code generation (#59) 13 | - SwiftUI-friendly Drawing API with KeyPath syntax for Swift 6.1+ (#62) 14 | - Equatable and Hashable conformance for Drawing struct (#71) 15 | - Native quadratic Bezier curve support (#89) 16 | - Xcode project integration support (Beta) (#77) 17 | - SVG stroke-miterlimit attribute support (#69) 18 | - Comprehensive demo applications (Demo app and plugin-demo) 19 | - Path extraction API for animations and custom rendering 20 | - Content mode support for flexible scaling options 21 | - Cross-platform support (iOS, macOS, SwiftUI, UIKit, AppKit) 22 | 23 | ### Changed 24 | - Refactored SVG parsers into separate focused modules (#81, #82) 25 | - Migrated to swift-parsing library for better parser composition (#80) 26 | - Renamed CGGenDemo to Demo for simplicity (#74) 27 | - Renamed plugin to cggen-spm-plugin for clarity (#72) 28 | - Optimized Drawing struct memory usage (#71) 29 | - Applied Swift 6 formatting and project cleanup (#78) 30 | - Improved code generation with better C bytecode formatting (#73) 31 | 32 | ### Fixed 33 | - Sanitized target names for Swift identifiers (#76) 34 | - Fixed whitespace parser crash in Swift internals (#58) 35 | - Fixed demo app UI issues (#68) 36 | 37 | ### Security 38 | - All generated code now uses compressed bytecode for smaller app bundles 39 | - No runtime file access required - all assets compiled at build time 40 | 41 | ## [0.1.0] - Initial Release 42 | 43 | ### Added 44 | - Basic SVG and PDF to Core Graphics code generation 45 | - Command-line interface 46 | - Support for common SVG elements and attributes 47 | - Objective-C code generation option 48 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/shadow_blur_radius.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Sources/PDFParse/PDFExtGState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct PDFSoftMask { 4 | public enum SubType: String { 5 | case alpha = "Alpha" 6 | case luminosity = "Luminosity" 7 | 8 | init(obj: PDFObject) throws { 9 | guard let name = obj.nameVal(), 10 | let value = SubType(rawValue: name) else { 11 | throw Error.parsingError() 12 | } 13 | self = value 14 | } 15 | } 16 | 17 | let subType: SubType 18 | let transparencyGroup: PDFXObject 19 | 20 | init(obj: PDFObject, xobjFactory: PDFXObject.Factory) throws { 21 | guard let dict = obj.dictionaryVal(), 22 | dict["Type"]?.nameVal() == "Mask", 23 | let subType = try dict["S"].map(SubType.init), 24 | let transparencyGroup = try dict["G"].map(xobjFactory) 25 | else { 26 | throw Error.parsingError() 27 | } 28 | self.subType = subType 29 | self.transparencyGroup = transparencyGroup 30 | } 31 | } 32 | 33 | public enum PDFGStateCommand { 34 | case fillAlpha(CGFloat) 35 | case strokeAlpha(CGFloat) 36 | case blendMode(String) 37 | case sMask(PDFSoftMask) 38 | } 39 | 40 | public struct PDFExtGState { 41 | public let commands: [PDFGStateCommand] 42 | init(obj: PDFObject, xobjFactory: PDFXObject.Factory) throws { 43 | guard let dict = obj.dictionaryVal() else { throw Error.parsingError() } 44 | commands = try dict.sorted(by: { $0.key < $1.key }).compactMap { (key, val) -> PDFGStateCommand? in 45 | switch key { 46 | case "Type": 47 | guard val.nameVal() == "ExtGState" else { throw Error.parsingError() } 48 | return nil 49 | case "ca": 50 | let alpha = val.realFromIntOrReal()! 51 | return .fillAlpha(alpha) 52 | case "CA": 53 | let alpha = val.realFromIntOrReal()! 54 | return .strokeAlpha(alpha) 55 | case "BM": 56 | let name = val.nameVal()! 57 | return .blendMode(name) 58 | case "SMask": 59 | let sMask = try PDFSoftMask(obj: val, xobjFactory: xobjFactory) 60 | return .sMask(sMask) 61 | default: 62 | throw Error.unsupported("graphical state command - '\(key)'") 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/dashes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dashes 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Sources/CGGenCLI/ObjCGen.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum ObjCGen { 4 | static func functionName(imageName: String, prefix: String) -> String { 5 | "\(prefix)Draw\(imageName)ImageInContext" 6 | } 7 | 8 | static func functionWithArgs(imageName: String, prefix: String) -> String { 9 | "void " 10 | .appending(functionName(imageName: imageName, prefix: prefix)) 11 | .appending("(CGContextRef context)") 12 | } 13 | 14 | static func functionDecl(imageName: String, prefix: String) -> String { 15 | functionWithArgs(imageName: imageName, prefix: prefix).appending(";") 16 | } 17 | 18 | static func functionDef(imageName: String, prefix: String) -> String { 19 | functionWithArgs(imageName: imageName, prefix: prefix).appending(" {") 20 | } 21 | 22 | static func cgFloatArray(_ array: [CGFloat]) -> String { 23 | let elements = array.map { "(CGFloat)\($0)" }.joined(separator: ", ") 24 | return "(CGFloat []){\(elements)}" 25 | } 26 | } 27 | 28 | extension GenerationParams { 29 | var descriptorTypename: String { 30 | prefix + module + "GeneratedImageDescriptor" 31 | } 32 | 33 | func descriptorName(for image: Image) -> String { 34 | "k" + prefix + module + image.name.upperCamelCase + "Descriptor" 35 | } 36 | 37 | var cggenSupportHeaderBody: String { 38 | supportHeader( 39 | prefix: prefix, 40 | module: module, 41 | descriptorTypeName: descriptorTypename 42 | ) 43 | } 44 | } 45 | 46 | private func supportHeader( 47 | prefix: String, 48 | module: String, 49 | descriptorTypeName: String 50 | ) -> String { 51 | """ 52 | \(commonHeaderPrefix) 53 | 54 | #if __has_feature(modules) 55 | @import CoreGraphics; 56 | @import CoreFoundation; 57 | #else 58 | #import 59 | #import 60 | #endif 61 | 62 | CF_ASSUME_NONNULL_BEGIN 63 | 64 | typedef struct CF_BRIDGED_TYPE(id) \(prefix)\(module)Resources *\(prefix)\( 65 | module 66 | )ResourcesRef CF_SWIFT_NAME(\(module)Resources); 67 | 68 | typedef struct { 69 | CGSize size; 70 | void (*drawingHandler)(CGContextRef); 71 | } \(descriptorTypeName) CF_SWIFT_NAME(\(module)Resources.Descriptor); 72 | 73 | CF_ASSUME_NONNULL_END 74 | 75 | """ 76 | } 77 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/shadow_colors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Sources/CGGenRTSupport/PlatformImageSupport+Deprecated.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import SwiftUI 3 | 4 | // MARK: - Deprecated KeyPath API 5 | 6 | extension CGGenPlatformImage { 7 | @MainActor 8 | @inlinable 9 | @available(*, deprecated, renamed: "draw(_:)") 10 | public static func draw(_ keyPath: KeyPath) 11 | -> CGGenPlatformImage { 12 | CGGenPlatformImage( 13 | drawing: Drawing.self[keyPath: keyPath], 14 | scale: defaultScale 15 | ) 16 | } 17 | 18 | @MainActor 19 | @inlinable 20 | @available(*, deprecated, renamed: "draw(_:size:contentMode:)") 21 | public static func draw( 22 | _ keyPath: KeyPath, 23 | size: CGSize, 24 | contentMode: DrawingContentMode = .aspectFit 25 | ) -> CGGenPlatformImage { 26 | CGGenPlatformImage( 27 | drawing: Drawing.self[keyPath: keyPath], 28 | size: size, 29 | contentMode: contentMode, 30 | scale: defaultScale 31 | ) 32 | } 33 | 34 | @inlinable 35 | @available(*, deprecated, renamed: "draw(_:scale:)") 36 | public static func draw( 37 | _ keyPath: KeyPath, 38 | scale: CGFloat 39 | ) -> CGGenPlatformImage { 40 | CGGenPlatformImage(drawing: Drawing.self[keyPath: keyPath], scale: scale) 41 | } 42 | 43 | @inlinable 44 | @available(*, deprecated, renamed: "draw(_:size:contentMode:scale:)") 45 | public static func draw( 46 | _ keyPath: KeyPath, 47 | size: CGSize, 48 | contentMode: DrawingContentMode = .aspectFit, 49 | scale: CGFloat 50 | ) -> CGGenPlatformImage { 51 | CGGenPlatformImage( 52 | drawing: Drawing.self[keyPath: keyPath], 53 | size: size, 54 | contentMode: contentMode, 55 | scale: scale 56 | ) 57 | } 58 | } 59 | 60 | extension Image { 61 | @MainActor 62 | @inlinable 63 | @available(*, deprecated, renamed: "draw(_:)") 64 | public static func draw(_ keyPath: KeyPath) -> Self { 65 | Self(drawing: Drawing.self[keyPath: keyPath], scale: defaultScale) 66 | } 67 | 68 | @inlinable 69 | @available(*, deprecated, renamed: "draw(_:scale:)") 70 | public static func draw( 71 | _ keyPath: KeyPath, 72 | scale: CGFloat 73 | ) -> Image { 74 | Image(drawing: Drawing.self[keyPath: keyPath], scale: scale) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "ae9d48e5c33a98b5e598a948da736e7fa2e0813dcab06de9d1445a8bc7464d25", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-argument-parser", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/apple/swift-argument-parser", 8 | "state" : { 9 | "revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3", 10 | "version" : "1.6.1" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-case-paths", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/pointfreeco/swift-case-paths", 17 | "state" : { 18 | "revision" : "9810c8d6c2914de251e072312f01d3bf80071852", 19 | "version" : "1.7.1" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-custom-dump", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/pointfreeco/swift-custom-dump", 26 | "state" : { 27 | "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", 28 | "version" : "1.3.3" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-parsing", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/pointfreeco/swift-parsing", 35 | "state" : { 36 | "revision" : "3432cb81164dd3d69a75d0d63205be5fbae2c34b", 37 | "version" : "0.14.1" 38 | } 39 | }, 40 | { 41 | "identity" : "swift-snapshot-testing", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing", 44 | "state" : { 45 | "revision" : "37230a37e83f1b7023be08e1b1a2603fcb1567fb", 46 | "version" : "1.18.4" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-syntax", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/swiftlang/swift-syntax", 53 | "state" : { 54 | "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2", 55 | "version" : "601.0.1" 56 | } 57 | }, 58 | { 59 | "identity" : "xctest-dynamic-overlay", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 62 | "state" : { 63 | "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", 64 | "version" : "1.5.2" 65 | } 66 | } 67 | ], 68 | "version" : 3 69 | } 70 | -------------------------------------------------------------------------------- /cggen.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "1f65457ff66c8e1df10865dffe64571be3febdb79e4fe7e777d72b92ebdcf828", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-argument-parser", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/apple/swift-argument-parser", 8 | "state" : { 9 | "revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3", 10 | "version" : "1.6.1" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-case-paths", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/pointfreeco/swift-case-paths", 17 | "state" : { 18 | "revision" : "9810c8d6c2914de251e072312f01d3bf80071852", 19 | "version" : "1.7.1" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-custom-dump", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/pointfreeco/swift-custom-dump", 26 | "state" : { 27 | "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", 28 | "version" : "1.3.3" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-parsing", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/pointfreeco/swift-parsing", 35 | "state" : { 36 | "revision" : "3432cb81164dd3d69a75d0d63205be5fbae2c34b", 37 | "version" : "0.14.1" 38 | } 39 | }, 40 | { 41 | "identity" : "swift-snapshot-testing", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing", 44 | "state" : { 45 | "revision" : "37230a37e83f1b7023be08e1b1a2603fcb1567fb", 46 | "version" : "1.18.4" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-syntax", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/swiftlang/swift-syntax", 53 | "state" : { 54 | "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2", 55 | "version" : "601.0.1" 56 | } 57 | }, 58 | { 59 | "identity" : "xctest-dynamic-overlay", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 62 | "state" : { 63 | "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", 64 | "version" : "1.5.2" 65 | } 66 | } 67 | ], 68 | "version" : 3 69 | } 70 | -------------------------------------------------------------------------------- /Sources/CGGenCLI/ObjcHeaderCGGenerator.swift: -------------------------------------------------------------------------------- 1 | import CGGenIR 2 | import CoreGraphics 3 | 4 | func generateObjCHeaderFile( 5 | params: GenerationParams, 6 | outputs: [Output] 7 | ) -> String { 8 | var result = "" 9 | 10 | // Header comment 11 | result += commonHeaderPrefix + "\n\n" 12 | 13 | // Imports 14 | result += params.imports + "\n\n" 15 | 16 | // Image functions 17 | let imageFunctions = outputs.map(\.image) 18 | .map { params.description(for: $0) } 19 | .joined(separator: "\n\n") 20 | if !imageFunctions.isEmpty { 21 | result += imageFunctions + "\n\n" 22 | } 23 | 24 | // Path functions 25 | let pathFunctions = outputs.flatMap(\.pathRoutines) 26 | .map { params.description(for: $0) } 27 | .joined(separator: "\n\n") 28 | if !pathFunctions.isEmpty { 29 | result += pathFunctions + "\n\n" 30 | } 31 | 32 | return result 33 | } 34 | 35 | extension GenerationParams { 36 | fileprivate var imports: String { 37 | switch style { 38 | case .plain: 39 | """ 40 | #if __has_feature(modules) 41 | @import CoreGraphics; 42 | #else // __has_feature(modules) 43 | #import 44 | #endif // __has_feature(modules) 45 | """ 46 | case .swiftFriendly: 47 | "#import \"cggen_support.h\"" 48 | } 49 | } 50 | } 51 | 52 | extension GenerationParams { 53 | fileprivate func description(for image: Image) -> String { 54 | let camel = image.name.upperCamelCase 55 | let imageSize = image.route.boundingRect.size 56 | 57 | switch style { 58 | case .plain: 59 | let functionDecl = ObjCGen.functionDecl(imageName: camel, prefix: prefix) 60 | return 61 | """ 62 | static const CGSize k\(prefix)\(camel)ImageSize = (CGSize){.width = \( 63 | imageSize 64 | .width 65 | ), .height = \(imageSize.height)}; 66 | \(functionDecl) 67 | """ 68 | case .swiftFriendly: 69 | let descriptorVarName = descriptorName(for: image) 70 | return 71 | """ 72 | extern const \(descriptorTypename) \(descriptorVarName) 73 | CF_SWIFT_NAME(\(descriptorTypename).\(image.name.lowerCamelCase)); 74 | """ 75 | } 76 | } 77 | 78 | fileprivate func description(for path: PathRoutine) -> String { 79 | let camel = path.id.upperCamelCase 80 | return "void \(prefix)\(camel)Path(CGMutablePathRef path);" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/CGGenRTSupport/README.md: -------------------------------------------------------------------------------- 1 | # CGGenRTSupport 2 | 3 | This module provides bytecode execution support for code generated by cggen, including the Drawing struct and utilities for creating images. 4 | 5 | ## Drawing Struct 6 | 7 | The core type is `Drawing`, which encapsulates a size and draw function: 8 | 9 | ```swift 10 | public struct Drawing { 11 | public let size: CGSize 12 | public let draw: (CGContext) -> Void 13 | } 14 | ``` 15 | 16 | ## SwiftUI Support 17 | 18 | Drawing conforms to View, enabling direct usage in SwiftUI: 19 | 20 | ```swift 21 | // In SwiftUI views 22 | Drawing.circle 23 | .foregroundColor(.blue) 24 | .frame(width: 44, height: 44) 25 | ``` 26 | 27 | ## Image Creation 28 | 29 | ### CGImage Creation 30 | 31 | ```swift 32 | // Create a CGImage from a Drawing 33 | if let cgImage = CGImage.draw(from: Drawing.star) { 34 | // Use cgImage... 35 | } 36 | 37 | // Create with custom scale 38 | let scaledImage = CGImage.draw( 39 | from: Drawing.star, 40 | scale: 2.0 41 | ) 42 | ``` 43 | 44 | ### Platform Image Creation 45 | 46 | ```swift 47 | // UIKit (iOS/tvOS/watchOS) 48 | #if canImport(UIKit) 49 | let uiImage = UIImage(Drawing.circle) 50 | let scaledImage = UIImage(Drawing.circle, scale: 3.0) 51 | #endif 52 | 53 | // AppKit (macOS) 54 | #if canImport(AppKit) 55 | let nsImage = NSImage(Drawing.square) 56 | #endif 57 | ``` 58 | 59 | ## Direct Drawing 60 | 61 | ### Drawing to CGContext 62 | 63 | ```swift 64 | // Draw directly using the draw function 65 | Drawing.star.draw(context) 66 | 67 | // Or use CGContext extension 68 | context.draw(Drawing.star, at: CGPoint(x: 10, y: 10)) 69 | ``` 70 | 71 | ## Usage with Generated Code 72 | 73 | When using `cggen` with `--generation-style swift-friendly`, generated code extends the Drawing namespace: 74 | 75 | ```swift 76 | // Generated by cggen 77 | extension Drawing { 78 | static let circle = Drawing( 79 | size: CGSize(width: 50, height: 50), 80 | draw: myDrawCircleFunction 81 | ) 82 | static let star = Drawing( 83 | size: CGSize(width: 60, height: 60), 84 | draw: myDrawStarFunction 85 | ) 86 | } 87 | 88 | // Usage 89 | Drawing.circle // Access generated drawings 90 | Drawing.star.size // Get size information 91 | ``` 92 | 93 | ## Error Handling 94 | 95 | CGImage creation returns an optional and may fail if graphics context creation fails. Platform image initializers (UIImage/NSImage) handle failures gracefully by returning empty images. 96 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pathGeneration.1.swift: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | import CoreGraphics 4 | @_spi(Generator) import CGGenRTSupport 5 | 6 | typealias Drawing = CGGenRTSupport.Drawing 7 | 8 | extension Drawing { 9 | static let paths = Drawing( 10 | width: 100.0, 11 | height: 100.0, 12 | bytecodeArray: mergedBytecodes, 13 | decompressedSize: 369, 14 | startIndex: 0, 15 | endIndex: 63 16 | ) 17 | } 18 | 19 | 20 | // MARK: - Paths 21 | 22 | extension Drawing.Path { 23 | static let simpleArrow = Drawing.Path( 24 | bytecodeArray: mergedBytecodes, 25 | decompressedSize: 369, 26 | startIndex: 64, 27 | endIndex: 117 28 | ) 29 | static let simpleHeart = Drawing.Path( 30 | bytecodeArray: mergedBytecodes, 31 | decompressedSize: 369, 32 | startIndex: 118, 33 | endIndex: 277 34 | ) 35 | static let simpleStar = Drawing.Path( 36 | bytecodeArray: mergedBytecodes, 37 | decompressedSize: 369, 38 | startIndex: 278, 39 | endIndex: 368 40 | ) 41 | } 42 | 43 | private let mergedBytecodes: [UInt8] = [ 44 | 0x62, 0x76, 0x78, 0x6E, 0x71, 0x01, 0x00, 0x00, 0xC9, 0x00, 0x00, 0x00, 45 | 0x60, 0x01, 0x00, 0xE6, 0x1A, 0x00, 0x00, 0x80, 0x3F, 0x00, 0xF9, 0x98, 46 | 0x08, 0x80, 0xBF, 0xEF, 0xC8, 0x42, 0x00, 0x24, 0xFF, 0xFF, 0xFF, 0x20, 47 | 0x0A, 0xD7, 0x23, 0x3C, 0x31, 0x06, 0x00, 0x30, 0x01, 0x88, 0x04, 0xC8, 48 | 0x42, 0xC0, 0x0C, 0x18, 0x32, 0x01, 0xEB, 0xA0, 0x41, 0x00, 0x00, 0x48, 49 | 0x42, 0x03, 0x00, 0x00, 0x8C, 0x42, 0x08, 0x09, 0x6E, 0x00, 0x00, 0x12, 50 | 0x40, 0x09, 0x70, 0x68, 0x12, 0x20, 0xF9, 0x4E, 0x70, 0x08, 0x29, 0xC8, 51 | 0x09, 0xC8, 0x41, 0x01, 0xF2, 0x98, 0x28, 0x70, 0x41, 0xE6, 0xA0, 0x40, 52 | 0x00, 0x00, 0xF0, 0x41, 0x08, 0x08, 0x58, 0x58, 0x01, 0x08, 0x11, 0x98, 53 | 0x21, 0x20, 0x41, 0xF1, 0x60, 0x32, 0x41, 0x08, 0x09, 0x98, 0x3A, 0x0C, 54 | 0x42, 0x68, 0x08, 0x96, 0xF1, 0x68, 0x11, 0x01, 0xF2, 0x98, 0x21, 0xB4, 55 | 0x42, 0x08, 0x08, 0x10, 0x32, 0x08, 0x09, 0x08, 0x43, 0x98, 0x53, 0xA0, 56 | 0x42, 0x50, 0x08, 0x8C, 0x58, 0x8B, 0x01, 0x08, 0x11, 0x28, 0x85, 0x08, 57 | 0x08, 0xC8, 0xA0, 0xC8, 0x41, 0x07, 0xF3, 0x98, 0xC4, 0x20, 0x41, 0xF3, 58 | 0x58, 0x45, 0x03, 0x10, 0x09, 0x46, 0x82, 0x4E, 0x70, 0x18, 0x6C, 0x58, 59 | 0x2D, 0x03, 0x48, 0x09, 0x8C, 0x98, 0x12, 0xC8, 0x41, 0xF1, 0x68, 0x24, 60 | 0x0C, 0x08, 0xAD, 0x10, 0x36, 0xE7, 0x20, 0x42, 0x00, 0x00, 0x20, 0x42, 61 | 0x07, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x76, 0x78, 62 | 0x24 63 | ] 64 | -------------------------------------------------------------------------------- /Tests/RegressionTests/__Snapshots__/CodeGenerationSnapshotTests/pathGeneration.3.m: -------------------------------------------------------------------------------- 1 | // Generated by cggen 2 | 3 | void runMergedBytecode(CGContextRef context, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 4 | void runPathBytecode(CGMutablePathRef path, const uint8_t* arr, int len); 5 | void runMergedPathBytecode(CGMutablePathRef path, const uint8_t* arr, int decompressedLen, int compressedLen, int startIndex, int endIndex); 6 | 7 | static const uint8_t mergedBytecodes[]; 8 | 9 | void testDrawPathsImageInContext(CGContextRef context) { 10 | runMergedBytecode(context, mergedBytecodes, 369, 217, 0, 63); 11 | } 12 | 13 | void testSimpleArrowPath(CGMutablePathRef path) { 14 | runMergedPathBytecode(path, mergedBytecodes, 369, 217, 64, 117); 15 | } 16 | 17 | void testSimpleHeartPath(CGMutablePathRef path) { 18 | runMergedPathBytecode(path, mergedBytecodes, 369, 217, 118, 277); 19 | } 20 | 21 | void testSimpleStarPath(CGMutablePathRef path) { 22 | runMergedPathBytecode(path, mergedBytecodes, 369, 217, 278, 368); 23 | } 24 | 25 | static const uint8_t mergedBytecodes[] = { 26 | 0x62, 0x76, 0x78, 0x6E, 0x71, 0x01, 0x00, 0x00, 0xC9, 0x00, 0x00, 0x00, 27 | 0x60, 0x01, 0x00, 0xE6, 0x1A, 0x00, 0x00, 0x80, 0x3F, 0x00, 0xF9, 0x98, 28 | 0x08, 0x80, 0xBF, 0xEF, 0xC8, 0x42, 0x00, 0x24, 0xFF, 0xFF, 0xFF, 0x20, 29 | 0x0A, 0xD7, 0x23, 0x3C, 0x31, 0x06, 0x00, 0x30, 0x01, 0x88, 0x04, 0xC8, 30 | 0x42, 0xC0, 0x0C, 0x18, 0x32, 0x01, 0xEB, 0xA0, 0x41, 0x00, 0x00, 0x48, 31 | 0x42, 0x03, 0x00, 0x00, 0x8C, 0x42, 0x08, 0x09, 0x6E, 0x00, 0x00, 0x12, 32 | 0x40, 0x09, 0x70, 0x68, 0x12, 0x20, 0xF9, 0x4E, 0x70, 0x08, 0x29, 0xC8, 33 | 0x09, 0xC8, 0x41, 0x01, 0xF2, 0x98, 0x28, 0x70, 0x41, 0xE6, 0xA0, 0x40, 34 | 0x00, 0x00, 0xF0, 0x41, 0x08, 0x08, 0x58, 0x58, 0x01, 0x08, 0x11, 0x98, 35 | 0x21, 0x20, 0x41, 0xF1, 0x60, 0x32, 0x41, 0x08, 0x09, 0x98, 0x3A, 0x0C, 36 | 0x42, 0x68, 0x08, 0x96, 0xF1, 0x68, 0x11, 0x01, 0xF2, 0x98, 0x21, 0xB4, 37 | 0x42, 0x08, 0x08, 0x10, 0x32, 0x08, 0x09, 0x08, 0x43, 0x98, 0x53, 0xA0, 38 | 0x42, 0x50, 0x08, 0x8C, 0x58, 0x8B, 0x01, 0x08, 0x11, 0x28, 0x85, 0x08, 39 | 0x08, 0xC8, 0xA0, 0xC8, 0x41, 0x07, 0xF3, 0x98, 0xC4, 0x20, 0x41, 0xF3, 40 | 0x58, 0x45, 0x03, 0x10, 0x09, 0x46, 0x82, 0x4E, 0x70, 0x18, 0x6C, 0x58, 41 | 0x2D, 0x03, 0x48, 0x09, 0x8C, 0x98, 0x12, 0xC8, 0x41, 0xF1, 0x68, 0x24, 42 | 0x0C, 0x08, 0xAD, 0x10, 0x36, 0xE7, 0x20, 0x42, 0x00, 0x00, 0x20, 0x42, 43 | 0x07, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x76, 0x78, 44 | 0x24 45 | }; 46 | -------------------------------------------------------------------------------- /.claude/commands/create-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | allowed-tools: Bash(gh release view:*), Bash(gh release list:*), Bash(gh release create:*), Bash(gh workflow run:*) 3 | description: Create a release 4 | --- 5 | 6 | Create $ARGUMENTS release 7 | 8 | ## Recent Releases 9 | - Previous releases: !git tag --sort=-creatordate | head -n 10 10 | 11 | - Current git status: !`git status` 12 | - Current branch: !`git branch --show-current` 13 | 14 | **ENSURE THERE ARE NO UNCOMMITTED CHANGES** 15 | **ENSURE YOUR BRANCH IS UP TO DATE WITH UPSTREAM REMOTE** 16 | **ENSURE `HEAD` EXISTS ON UPSTREAM REMOTE** 17 | 18 | **IF ANY OF THESE CONDITIONS ARE NOT MET, EXIT NOW** 19 | 20 | ## Process 21 | 22 | 1. **Validate version format** (must be semantic versioning x.y.z) 23 | 24 | 2. **Review recent releases** to understand the format and style: 25 | - Use `gh release view` to check formatting conventions 26 | - Note emoji usage, section organization, and tone 27 | 28 | 3. **Analyze changes** since previous release: 29 | - Find previous version 30 | - Check diff between previous and current version 31 | - If release is minor or patch, research breaking changes 32 | 33 | 4. **Create release notes** in a temporary RELEASE_$ARGUMENTS.md file: 34 | - Match the style and formatting of recent releases 35 | - Include appropriate emoji headers (🚀, ✨, 🆕, etc.) 36 | - Organize sections consistently with previous releases 37 | - List changes with PR/commit references 38 | 39 | 5. **Ask user for confirmation** with RELEASE_$ARGUMENTS.md content 40 | - If user does not confirm, remove RELEASE_$ARGUMENTS.md and exit 41 | 42 | 6. **Determine target branch**: 43 | - Default: `main` for regular releases 44 | - Ask user if releasing from a different branch (e.g., hotfix from `release-1.x`) 45 | 46 | 7. **Create draft release**: 47 | ```bash 48 | gh release create $ARGUMENTS --draft --target --title "cggen $ARGUMENTS" --notes-file RELEASE_$ARGUMENTS.md --repo yandex/cggen 49 | ``` 50 | 51 | 8. **Ask user** if they want to run the release workflow now 52 | 53 | 9. **Run release workflow** (if user confirms): 54 | ```bash 55 | gh workflow run release.yml --repo yandex/cggen 56 | ``` 57 | - Workflow will: update podspecs, run tests, create tag, publish release 58 | - Link to monitor: https://github.com/yandex/cggen/actions/workflows/release.yml 59 | 60 | 10. **Clean up**: Remove RELEASE_$ARGUMENTS.md 61 | 62 | **NOTE**: Do NOT manually update podspecs, create tags, or publish the release. The workflow handles all of this automatically. 63 | -------------------------------------------------------------------------------- /Tests/RegressionTests/PDFTests.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | import Testing 4 | 5 | import CGGenCore 6 | import CGGenDiagnosticSupport 7 | 8 | @Suite struct PDFTests { 9 | @Test func alpha() { 10 | test(pdf: "alpha") 11 | } 12 | 13 | @Test func capsJoins() { 14 | test(pdf: "caps_joins") 15 | } 16 | 17 | @Test func dashes() { 18 | test(pdf: "dashes") 19 | } 20 | 21 | @Test func fill() { 22 | test(pdf: "fill") 23 | } 24 | 25 | @Test func gradient() { 26 | test(pdf: "gradient") 27 | } 28 | 29 | @Test func gradientRadial() { 30 | test(pdf: "gradient_radial") 31 | } 32 | 33 | @Test func gradientShape() { 34 | test(pdf: "gradient_shape") 35 | } 36 | 37 | @Test func gradientThreeDots() { 38 | test(pdf: "gradient_three_dots") 39 | } 40 | 41 | @Test func gradientWithAlpha() { 42 | test(pdf: "gradient_with_alpha") 43 | } 44 | 45 | @Test func gradientWithMask() { 46 | test(pdf: "gradient_with_mask") 47 | } 48 | 49 | @Test func groupOpacity() { 50 | test(pdf: "group_opacity") 51 | } 52 | 53 | @Test func lines() { 54 | test(pdf: "lines") 55 | } 56 | 57 | @Test func nestedTransparentGroup() { 58 | test(pdf: "nested_transparent_group", tolerance: 0.005) 59 | } 60 | 61 | @Test func shapes() { 62 | test(pdf: "shapes", tolerance: 0.005) 63 | } 64 | 65 | @Test func underlyingObjectWithTinyAlpha() { 66 | test(pdf: "underlying_object_with_tiny_alpha") 67 | } 68 | 69 | @Test func whiteCrossScnOperator() { 70 | test(pdf: "white_cross_scn_operator") 71 | } 72 | } 73 | 74 | private let defaultTolerance = 0.003 75 | private let defaultScale = 2.0 76 | 77 | private func test( 78 | pdf: String, 79 | tolerance: Double = defaultTolerance, 80 | scale: CGFloat = defaultScale 81 | ) { 82 | do { 83 | let path = sample(named: pdf) 84 | let pdf = CGPDFDocument(path as CFURL)! 85 | try CGGenCore.check(pdf.pages.count == 1, Err("multipage pdf")) 86 | let reference = try 87 | pdf.pages[0].render(scale: scale) !! Err("Couldnt create png from \(pdf)") 88 | try testBC( 89 | path: path, 90 | referenceRenderer: { _ in reference }, 91 | scale: scale, 92 | antialiasing: false, 93 | tolerance: tolerance 94 | ) 95 | } catch { 96 | Issue.record("Unexpected error: \(error)") 97 | } 98 | } 99 | 100 | private func sample(named name: String) -> URL { 101 | samplesPath.appendingPathComponent(name).appendingPathExtension("pdf") 102 | } 103 | 104 | private let samplesPath = 105 | getCurrentFilePath().appendingPathComponent("pdf_samples") 106 | -------------------------------------------------------------------------------- /Demo/CLAUDE.md: -------------------------------------------------------------------------------- 1 | # Demo Workflow 2 | 3 | This document helps Claude Code work with the Demo app effectively. 4 | 5 | ## Overview 6 | 7 | Demo is a demonstration app showcasing cggen's drawing APIs across SwiftUI, AppKit, and a playground environment. The app accepts command-line arguments to launch directly into specific tabs. 8 | 9 | ## Tabs 10 | 11 | - **SwiftUI/AppKit/UIKit tabs**: Show code examples of cggen API usage with live previews, organized by categories like image creation, sizing, and UI integration. 12 | - **Playground tab**: Interactive environment for testing different drawings with adjustable size, content mode, and scale settings. 13 | 14 | ## Command Line Arguments 15 | 16 | - `--tab [swiftui|appkit|playground]` - Launch with specific tab (default: swiftui) 17 | 18 | ## Building and Running 19 | 20 | To build and run Demo, use these MCP tools in sequence: 21 | 22 | 1. **Build the app** 23 | - Tool: `mcp__XcodeBuildMCP__build_mac_proj` 24 | - projectPath: `/Users/alfred/dev/cggen/Demo/Demo.xcodeproj` 25 | - scheme: `Demo` 26 | 27 | 2. **Get the built app path** 28 | - Tool: `mcp__XcodeBuildMCP__get_mac_app_path_proj` 29 | - projectPath: `/Users/alfred/dev/cggen/Demo/Demo.xcodeproj` 30 | - scheme: `Demo` 31 | 32 | 3. **Launch the app** 33 | - Tool: `mcp__XcodeBuildMCP__launch_mac_app` 34 | - appPath: (path from step 2) 35 | - args: (optional, e.g. `["--tab", "appkit"]`) 36 | 37 | ## Taking Screenshots 38 | 39 | ### Screenshot workflow 40 | 1. Launch app with desired tab 41 | 2. Remove any existing screenshot file if present 42 | 3. Capture screenshot immediately without sleep/delay: 43 | 44 | ```bash 45 | shortcuts run "cggendemo" --output-path .claude_temp/screenshot_name.png 46 | ``` 47 | 48 | ### Screenshot storage 49 | - Screenshots are saved in `.claude_temp/` folder 50 | - This folder is gitignored to keep the repository clean 51 | - Always remove old screenshots before capturing new ones 52 | 53 | ### Shortcuts Setup 54 | - The `cggendemo.shortcut` file is included in this folder 55 | - Import it to macOS Shortcuts app if needed 56 | - This shortcut captures the Demo window and outputs PNG data 57 | 58 | ## Demo App Architecture 59 | 60 | - **SwiftUI tab**: Shows SwiftUI API usage with Drawing views 61 | - **AppKit tab**: Shows AppKit/NSImage API usage 62 | - **Playground tab**: Interactive demo for testing different drawings and sizes 63 | 64 | ## Key Fixes Applied 65 | 66 | 1. **Drawing view scaling**: Fixed to respect frame constraints 67 | 2. **Coordinate system**: Fixed upside-down images in SwiftUI by flipping Canvas coordinates 68 | 3. **AppKit layout**: Using FlippedView for correct coordinate system 69 | 4. **UI sizing**: Icons at 16pt, larger drawings at 24-40pt for better visual hierarchy 70 | -------------------------------------------------------------------------------- /Sources/CGGenRTSupport/CGImageSupport.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | 4 | // MARK: - SwiftUI Support 5 | 6 | #if canImport(SwiftUI) 7 | import SwiftUI 8 | 9 | extension Drawing: View { 10 | public var body: some View { 11 | if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) { 12 | Canvas { context, canvasSize in 13 | context.withCGContext { cgContext in 14 | // Calculate scale to fit the canvas 15 | let scaleX = canvasSize.width / size.width 16 | let scaleY = canvasSize.height / size.height 17 | let scale = min(scaleX, scaleY) 18 | 19 | // Center the drawing 20 | let scaledWidth = size.width * scale 21 | let scaledHeight = size.height * scale 22 | let offsetX = (canvasSize.width - scaledWidth) / 2 23 | let offsetY = (canvasSize.height - scaledHeight) / 2 24 | 25 | // Flip the coordinate system to match UIKit/AppKit 26 | cgContext.translateBy(x: 0, y: canvasSize.height) 27 | cgContext.scaleBy(x: 1, y: -1) 28 | 29 | // Apply centering and scaling 30 | cgContext.translateBy(x: offsetX, y: offsetY) 31 | cgContext.scaleBy(x: scale, y: scale) 32 | 33 | draw(in: cgContext) 34 | } 35 | } 36 | .aspectRatio(size, contentMode: .fit) 37 | } else { 38 | Image(drawing: self) 39 | .renderingMode(.original) 40 | .resizable() 41 | .aspectRatio(contentMode: .fit) 42 | } 43 | } 44 | } 45 | #endif 46 | 47 | extension CGImage { 48 | public static func draw( 49 | from descriptor: Drawing, 50 | scale: CGFloat, 51 | colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB() 52 | ) -> CGImage? { 53 | let size = descriptor.size 54 | let scaledSize = CGSize( 55 | width: size.width * scale, 56 | height: size.height * scale 57 | ) 58 | 59 | let bytesPerRow = Int(scaledSize.width) * 4 60 | let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue 61 | 62 | guard let context = CGContext( 63 | data: nil, 64 | width: Int(scaledSize.width), 65 | height: Int(scaledSize.height), 66 | bitsPerComponent: 8, 67 | bytesPerRow: bytesPerRow, 68 | space: colorSpace, 69 | bitmapInfo: bitmapInfo 70 | ) else { 71 | return nil 72 | } 73 | 74 | if scale != 1.0 { 75 | context.scaleBy(x: scale, y: scale) 76 | } 77 | 78 | descriptor.draw(in: context) 79 | 80 | return context.makeImage() 81 | } 82 | } 83 | 84 | extension CGContext { 85 | public func draw( 86 | _ descriptor: Drawing, 87 | at origin: CGPoint = .zero 88 | ) { 89 | saveGState() 90 | defer { restoreGState() } 91 | 92 | translateBy(x: origin.x, y: origin.y) 93 | descriptor.draw(in: self) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/CGGenRuntime/SVGRenderer.swift: -------------------------------------------------------------------------------- 1 | import CGGenIR 2 | import CGGenRTSupport 3 | @preconcurrency import CoreGraphics 4 | import Foundation 5 | import SVGParse 6 | 7 | /// Creates CGImage from SVG data 8 | public enum SVGRenderer { 9 | public enum Error: Swift.Error { 10 | case invalidSize 11 | case contextCreationFailed 12 | case invalidUTF8String 13 | } 14 | 15 | /// Create a CGImage from SVG data 16 | /// - Parameters: 17 | /// - data: The SVG content as Data 18 | /// - size: The size to render the SVG at. If nil, uses the SVG's natural 19 | /// size 20 | /// - scale: The scale factor to apply 21 | /// - Returns: A CGImage containing the rendered SVG 22 | /// - Throws: Error if SVG parsing or rendering fails 23 | public static func createCGImage( 24 | from data: Data, 25 | size: CGSize? = nil, 26 | scale: CGFloat = 1.0 27 | ) throws -> CGImage { 28 | let svg = try SVGParser.root(from: data) 29 | let routines = try SVGToDrawRouteConverter.convert(document: svg) 30 | let svgBounds = routines.drawRoutine.boundingRect 31 | 32 | let targetSize: CGSize 33 | let effectiveScale: CGFloat 34 | 35 | if let size { 36 | guard size.width > 0, size.height > 0 else { 37 | throw Error.invalidSize 38 | } 39 | targetSize = CGSize( 40 | width: size.width * scale, 41 | height: size.height * scale 42 | ) 43 | let scaleX = size.width / svgBounds.width 44 | let scaleY = size.height / svgBounds.height 45 | effectiveScale = min(scaleX, scaleY) * scale 46 | } else { 47 | // Use natural size 48 | targetSize = CGSize( 49 | width: svgBounds.width * scale, 50 | height: svgBounds.height * scale 51 | ) 52 | effectiveScale = scale 53 | } 54 | 55 | var scaledRoutine = routines.drawRoutine 56 | if effectiveScale != 1.0 { 57 | scaledRoutine.steps = [ 58 | .saveGState, 59 | .concatCTM(CGAffineTransform( 60 | scaleX: effectiveScale, 61 | y: effectiveScale 62 | )), 63 | ] + scaledRoutine.steps + [.restoreGState] 64 | } 65 | 66 | let bytecode = generateRouteBytecode(route: scaledRoutine) 67 | 68 | let colorSpace = CGColorSpaceCreateDeviceRGB() 69 | let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue 70 | 71 | guard let context = CGContext( 72 | data: nil, 73 | width: Int(targetSize.width), 74 | height: Int(targetSize.height), 75 | bitsPerComponent: 8, 76 | bytesPerRow: 0, 77 | space: colorSpace, 78 | bitmapInfo: bitmapInfo 79 | ) else { 80 | throw Error.contextCreationFailed 81 | } 82 | 83 | try runBytecode(context, fromData: Data(bytecode)) 84 | 85 | guard let image = context.makeImage() else { 86 | throw Error.contextCreationFailed 87 | } 88 | 89 | return image 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/RegressionTests/svg_samples/transforms.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Sources/SVGParse/SVGAttributeGroupParser.swift: -------------------------------------------------------------------------------- 1 | import CGGenCore 2 | import Foundation 3 | @preconcurrency import Parsing 4 | 5 | enum SVGAttributeGroupParser { 6 | typealias Attribute = SVGAttributeParser 7 | 8 | // MARK: - Common parsers 9 | 10 | static let x = Attribute.Coord(.x) 11 | static let y = Attribute.Coord(.y) 12 | static let width = Attribute.Len(.width) 13 | static let height = Attribute.Len(.height) 14 | 15 | // MARK: - Group Parsers 16 | 17 | static let version = Attribute.Identifier(.version).compactMap { value in 18 | if let version = value { 19 | version == "1.1" ? () : nil 20 | } else { 21 | () 22 | } 23 | } 24 | 25 | static let xml = Parse { _, _ in () } with: { 26 | Attribute.Identifier(.xmlns) 27 | Attribute.Identifier(.xmlnsxlink) 28 | } 29 | 30 | static let ignore = Optionally { 31 | DicitionaryKey(SVGParse.Attribute.maskType.rawValue) 32 | } 33 | .map { _ in () } 34 | 35 | static let presentation = Parse(SVG.PresentationAttributes.init) { 36 | Attribute.Funciri(.clipPath) 37 | Attribute.FillRule(.clipRule) 38 | Attribute.Funciri(.mask) 39 | Attribute.Funciri(.filter) 40 | Attribute.Paint(.fill) 41 | Attribute.FillRule(.fillRule) 42 | Attribute.Num(.fillOpacity) 43 | Attribute.Paint(.stroke) 44 | Attribute.Len(.strokeWidth) 45 | Attribute.LineCap(.strokeLinecap) 46 | Attribute.LineJoin(.strokeLinejoin) 47 | Attribute.DashArray(.strokeDasharray) 48 | Attribute.Len(.strokeDashoffset) 49 | Attribute.Num(.strokeOpacity) 50 | Attribute.Num(.strokeMiterlimit) 51 | Attribute.Num(.opacity) 52 | Attribute.Color(.stopColor) 53 | Attribute.Num(.stopOpacity) 54 | Attribute.ColorInterpolation(.colorInterpolationFilters) 55 | } 56 | 57 | static let core = Attribute.Identifier(.id).map(SVG.CoreAttributes.init) 58 | 59 | static let stop = Parse(SVG.Stop.init) { 60 | core 61 | presentation 62 | Attribute.StopOffset(.offset) 63 | } 64 | 65 | static let filterPrimitiveAttributes = Parse(SVG 66 | .FilterPrimitiveCommonAttributes.init 67 | ) { 68 | Attribute.Identifier(.result) 69 | height 70 | width 71 | x 72 | y 73 | } 74 | 75 | static let filterAttributes = Parse(SVG.FilterAttributes.init) { 76 | core 77 | presentation 78 | x 79 | y 80 | width 81 | height 82 | Attribute.Units(.filterUnits) 83 | } 84 | 85 | // MARK: - Filter Primitive Parser 86 | 87 | struct FilterPrimitive: Parser 88 | where Data.Input == [String: String], Data.Output: Equatable { 89 | let data: Data 90 | 91 | init(_ data: Data) { 92 | self.data = data 93 | } 94 | 95 | var body: some Parser< 96 | [String: String], 97 | SVG.FilterPrimitiveElement 98 | > { 99 | Parse(SVG.FilterPrimitiveElement.init) { 100 | core 101 | presentation 102 | filterPrimitiveAttributes 103 | data 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Sources/PDFParse/PDFOperator.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | 4 | public enum PDFOperator { 5 | // b 6 | case closeFillStrokePathWinding 7 | // B 8 | case fillStrokePathWinding 9 | // b* 10 | case closeFillStrokePathEvenOdd 11 | // B* 12 | case fillStrokePathEvenOdd 13 | // BDC 14 | case markedContentSequenceWithPListBegin 15 | // BI 16 | case inlineImageBegin 17 | // BMC 18 | case markedContentSequenceBegin 19 | // BT 20 | case textObjectBegin 21 | // BX 22 | case compatabilitySectionBegin 23 | // c 24 | case curveTo(CGPoint, CGPoint, CGPoint) 25 | // cm 26 | case concatCTM(CGAffineTransform) 27 | // CS 28 | case colorSpaceStroke(String) 29 | // cs 30 | case colorSpaceNonstroke(String) 31 | // d 32 | case dash(CGFloat, [CGFloat]) 33 | // d0 34 | case glyphWidthInType3Font 35 | // d1 36 | case glyphWidthAndBoundingBoxInType3Font 37 | // Do 38 | case invokeXObject(String) 39 | // DP 40 | case markedContentPointWithPListDefine 41 | // EI 42 | case inlineImageEnd 43 | // EMC 44 | case markedContentSequenceEnd 45 | // ET 46 | case textObjectEnd 47 | // EX 48 | case compatabilitySectionEnd 49 | // f, F 50 | case fillWinding 51 | // f* 52 | case fillEvenOdd 53 | // G 54 | case grayLevelStroke 55 | // g 56 | case grayLevelNonstroke 57 | // gs 58 | case applyGState(String) 59 | // h 60 | case closeSubpath 61 | // i 62 | case setFlatnessTolerance(CGFloat) 63 | // ID 64 | case inlineImageDataBegin 65 | // j 66 | case lineJoinStyle(Int) 67 | // J 68 | case lineCapStyle(Int) 69 | // K 70 | case cmykColorStroke 71 | // k 72 | case cmykColorNonstroke 73 | // l 74 | case lineTo(CGPoint) 75 | // m 76 | case moveTo(CGPoint) 77 | // M 78 | case miterLimit 79 | // MP 80 | case markedContentPointDefine 81 | // n 82 | case endPath 83 | // q 84 | case saveGState 85 | // Q 86 | case restoreGState 87 | // re 88 | case appendRectangle(CGRect) 89 | // RG 90 | case rgbColorStroke(PDFColor) 91 | // rg 92 | case rgbColorNonstroke(PDFColor) 93 | // ri 94 | case colorRenderingIntent(String) 95 | // s 96 | case closeAndStrokePath 97 | // S 98 | case strokePath 99 | // SC 100 | case colorStroke(PDFColor) 101 | // sc 102 | case colorNonstroke(PDFColor) 103 | // SCN 104 | case iccOrSpecialColorStroke 105 | // scn 106 | case iccOrSpecialColorNonstroke 107 | // sh 108 | case shadingFill(String) 109 | // T* 110 | case startNextTextLine 111 | // Tc 112 | case characterSpacing 113 | // Td 114 | case moveTextPosition 115 | // TD 116 | case moveTextPositionAnsSetLeading 117 | // Tf 118 | case textFontAndSize 119 | // Tj 120 | case showText 121 | // TJ 122 | case showTextAllowingIndividualGlyphPositioning 123 | // TL 124 | case textLeading 125 | // Tm 126 | case textAndTextLineMatrix 127 | // Tr 128 | case textRenderingMode 129 | // Ts 130 | case textRise 131 | // Tw 132 | case wordSpacing 133 | // Tz 134 | case horizontalTextScaling 135 | // v 136 | case curveToWithInitailPointReplicated 137 | // w 138 | case lineWidth(CGFloat) 139 | // W 140 | case clipWinding 141 | // W* 142 | case clipEvenOdd 143 | // y 144 | case curveToWithFinalPointReplicated 145 | // ' 146 | case moveToNextLineAndShowText 147 | // " 148 | case wordAndCharacterSpacingMoveToNextLineAndShowText 149 | } 150 | -------------------------------------------------------------------------------- /Demo/Demo/SwiftUIDemo.swift: -------------------------------------------------------------------------------- 1 | import CGGenRTSupport 2 | import SwiftUI 3 | 4 | struct SwiftUIDemo: View { 5 | var body: some View { 6 | List { 7 | // Direct Drawing usage 8 | Section("Drawing as View") { 9 | HStack { 10 | Drawing.star 11 | .frame(width: 30, height: 30) 12 | Spacer() 13 | Text("Drawing.star") 14 | } 15 | 16 | HStack { 17 | Drawing.heart 18 | .foregroundColor(.red) 19 | .frame(width: 30, height: 30) 20 | Spacer() 21 | Text("Drawing.heart.foregroundColor(.red)") 22 | } 23 | } 24 | 25 | // Frame and sizing 26 | Section("Sizing") { 27 | HStack { 28 | Drawing.gear 29 | .frame(width: 30, height: 30) 30 | Spacer() 31 | Text(".frame(width: 30, height: 30)") 32 | } 33 | 34 | HStack { 35 | Drawing.mountain 36 | .aspectRatio(contentMode: .fit) 37 | .frame(height: 40) 38 | .background(Color.gray.opacity(0.1)) 39 | Spacer() 40 | Text(".aspectRatio(contentMode: .fit)") 41 | } 42 | } 43 | 44 | // Image conversion 45 | Section("Image(drawing:)") { 46 | HStack { 47 | Image(drawing: .rocket) 48 | .resizable() 49 | .frame(width: 30, height: 30) 50 | Spacer() 51 | Text("Image(drawing: .rocket).resizable()") 52 | } 53 | 54 | HStack { 55 | Image(drawing: .star, scale: 2.0) 56 | .foregroundColor(.orange) 57 | Spacer() 58 | Text("Image(drawing: .star, scale: 2.0)") 59 | } 60 | } 61 | 62 | // Modifiers 63 | Section("Modifiers") { 64 | HStack { 65 | Drawing.gear 66 | .rotationEffect(.degrees(45)) 67 | .frame(width: 30, height: 30) 68 | Spacer() 69 | Text(".rotationEffect(.degrees(45))") 70 | } 71 | 72 | HStack { 73 | Drawing.heart 74 | .scaleEffect(1.5) 75 | .foregroundColor(.pink) 76 | .frame(width: 45, height: 45) 77 | Spacer() 78 | Text(".scaleEffect(1.5)") 79 | } 80 | 81 | HStack { 82 | Drawing.rocket 83 | .opacity(0.5) 84 | .frame(width: 30, height: 30) 85 | Spacer() 86 | Text(".opacity(0.5)") 87 | } 88 | } 89 | 90 | // In buttons 91 | Section("Interactive") { 92 | HStack { 93 | Button(action: {}) { 94 | Drawing.star 95 | .frame(width: 20, height: 20) 96 | } 97 | .buttonStyle(.bordered) 98 | Spacer() 99 | Text("Button with Drawing") 100 | } 101 | 102 | HStack { 103 | Button(action: {}) { 104 | Label { 105 | Text("Navigate") 106 | } icon: { 107 | Drawing.mountain 108 | .frame(width: 16, height: 16) 109 | } 110 | } 111 | .buttonStyle(.borderedProminent) 112 | Spacer() 113 | Text("Label with icon") 114 | } 115 | } 116 | } 117 | .navigationTitle("SwiftUI API") 118 | #if os(iOS) 119 | .navigationBarTitleDisplayMode(.inline) 120 | #endif 121 | } 122 | } 123 | 124 | #Preview { 125 | SwiftUIDemo() 126 | } 127 | -------------------------------------------------------------------------------- /cggen.xcworkspace/xcshareddata/xcschemes/RegressionTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 44 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /.github/workflows/nightly-extended-tests.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Extended Test Suite 2 | 3 | on: 4 | schedule: 5 | # Runs at 2 AM UTC every day (adjust time as needed) 6 | - cron: '0 2 * * *' 7 | workflow_dispatch: # Allows manual triggering from GitHub UI 8 | 9 | jobs: 10 | check-for-changes: 11 | runs-on: ubuntu-latest 12 | outputs: 13 | should-run: ${{ steps.check.outputs.should-run }} 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 2 18 | 19 | - name: Check for recent commits 20 | id: check 21 | run: | 22 | # Get commits from last 24 hours 23 | LAST_COMMIT_TIME=$(git log -1 --format=%ct) 24 | CURRENT_TIME=$(date +%s) 25 | TIME_DIFF=$((CURRENT_TIME - LAST_COMMIT_TIME)) 26 | 27 | # 86400 seconds = 24 hours 28 | if [ $TIME_DIFF -lt 86400 ]; then 29 | echo "New commits found in the last 24 hours" 30 | echo "should-run=true" >> $GITHUB_OUTPUT 31 | else 32 | echo "No new commits in the last 24 hours" 33 | echo "should-run=false" >> $GITHUB_OUTPUT 34 | fi 35 | 36 | flaky-tests: 37 | needs: check-for-changes 38 | if: needs.check-for-changes.outputs.should-run == 'true' 39 | runs-on: macos-15 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | 44 | - name: Setup Xcode 45 | uses: maxim-lobanov/setup-xcode@v1 46 | with: 47 | xcode-version: latest-stable 48 | 49 | - name: Build 50 | run: swift build --enable-experimental-prebuilts 51 | 52 | - name: Run gradient determinism test multiple times 53 | run: | 54 | echo "Running gradient determinism test 5 times without deterministic hashing..." 55 | for i in {1..5}; do 56 | echo "Run $i of 5" 57 | swift test --filter "gradientDeterminismGeneration" || { 58 | echo "❌ Test failed on run $i" 59 | exit 1 60 | } 61 | done 62 | echo "✅ All 5 runs passed successfully!" 63 | 64 | - name: Run all tests including extended tests 65 | run: | 66 | echo "Running all tests including extended tests..." 67 | CGGEN_EXTENDED_TESTS=1 swift test --parallel || { 68 | echo "❌ Tests failed" 69 | exit 1 70 | } 71 | echo "✅ All tests passed!" 72 | 73 | - name: Create issue on failure 74 | if: failure() 75 | uses: actions/github-script@v7 76 | with: 77 | script: | 78 | const title = 'Nightly flaky test failure'; 79 | const body = `The nightly flaky test run failed. This likely indicates non-deterministic behavior in the code. 80 | 81 | [View failed workflow run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) 82 | 83 | Please investigate the failure.`; 84 | 85 | // Check if issue already exists 86 | const issues = await github.rest.issues.listForRepo({ 87 | owner: context.repo.owner, 88 | repo: context.repo.repo, 89 | state: 'open', 90 | labels: ['flaky-test'] 91 | }); 92 | 93 | if (issues.data.length === 0) { 94 | await github.rest.issues.create({ 95 | owner: context.repo.owner, 96 | repo: context.repo.repo, 97 | title: title, 98 | body: body, 99 | labels: ['flaky-test', 'bug'] 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /Sources/CGGenRuntime/SVGSupport.swift: -------------------------------------------------------------------------------- 1 | import CGGenRTSupport 2 | import CoreGraphics 3 | import Foundation 4 | import SwiftUI 5 | 6 | #if canImport(UIKit) 7 | import UIKit 8 | #elseif canImport(AppKit) 9 | import AppKit 10 | #endif 11 | 12 | // MARK: - CGImage SVG Support 13 | 14 | extension CGImage { 15 | /// Creates a CGImage from SVG data 16 | public static func svg( 17 | _ data: Data, 18 | size: CGSize? = nil, 19 | scale: CGFloat = 1.0 20 | ) throws -> CGImage { 21 | try SVGRenderer.createCGImage(from: data, size: size, scale: scale) 22 | } 23 | 24 | /// Creates a CGImage from SVG string 25 | public static func svg( 26 | _ string: String, 27 | size: CGSize? = nil, 28 | scale: CGFloat = 1.0 29 | ) throws -> CGImage { 30 | guard let data = string.data(using: .utf8) else { 31 | throw SVGRenderer.Error.invalidUTF8String 32 | } 33 | return try svg(data, size: size, scale: scale) 34 | } 35 | } 36 | 37 | // MARK: - Platform Image SVG Support 38 | 39 | extension CGGenPlatformImage { 40 | // MARK: @MainActor methods using default scale 41 | 42 | @MainActor 43 | public convenience init(svgData: Data, size: CGSize) throws { 44 | #if canImport(UIKit) 45 | let scale = UIScreen.main.scale 46 | #elseif canImport(AppKit) 47 | let scale = NSScreen.main?.backingScaleFactor ?? 1.0 48 | #endif 49 | try self.init(svgData: svgData, size: size, scale: scale) 50 | } 51 | 52 | @MainActor 53 | public convenience init(svgString: String, size: CGSize) throws { 54 | #if canImport(UIKit) 55 | let scale = UIScreen.main.scale 56 | #elseif canImport(AppKit) 57 | let scale = NSScreen.main?.backingScaleFactor ?? 1.0 58 | #endif 59 | try self.init(svgString: svgString, size: size, scale: scale) 60 | } 61 | 62 | // MARK: Methods with explicit scale 63 | 64 | public convenience init(svgData: Data, size: CGSize, scale: CGFloat) throws { 65 | let cgImage = try CGImage.svg(svgData, size: size, scale: scale) 66 | #if canImport(UIKit) 67 | self.init(cgImage: cgImage, scale: scale, orientation: .up) 68 | #elseif canImport(AppKit) 69 | self.init(cgImage: cgImage, size: size) 70 | #endif 71 | } 72 | 73 | public convenience init( 74 | svgString: String, 75 | size: CGSize, 76 | scale: CGFloat 77 | ) throws { 78 | guard let data = svgString.data(using: .utf8) else { 79 | throw SVGRenderer.Error.invalidUTF8String 80 | } 81 | try self.init(svgData: data, size: size, scale: scale) 82 | } 83 | } 84 | 85 | // MARK: - SwiftUI Image SVG Support 86 | 87 | extension Image { 88 | @MainActor 89 | public init(svgData: Data, size: CGSize) throws { 90 | let image = try CGGenPlatformImage(svgData: svgData, size: size) 91 | self.init(platformImage: image) 92 | } 93 | 94 | @MainActor 95 | public init(svgString: String, size: CGSize) throws { 96 | let image = try CGGenPlatformImage(svgString: svgString, size: size) 97 | self.init(platformImage: image) 98 | } 99 | 100 | public init(svgData: Data, size: CGSize, scale: CGFloat) throws { 101 | let image = try CGGenPlatformImage( 102 | svgData: svgData, 103 | size: size, 104 | scale: scale 105 | ) 106 | self.init(platformImage: image) 107 | } 108 | 109 | public init(svgString: String, size: CGSize, scale: CGFloat) throws { 110 | let image = try CGGenPlatformImage( 111 | svgString: svgString, 112 | size: size, 113 | scale: scale 114 | ) 115 | self.init(platformImage: image) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Sources/PDFParse/PDFXObject.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | 4 | public struct PDFXObject { 5 | private static let unsupportedKeys: Set = 6 | ["Ref", "Metadata", "PieceInfo", "StructParent", "StructParents", "OPI"] 7 | private static let unsupportedSubtypes: Set = ["Image"] 8 | 9 | public let operators: [PDFOperator] 10 | public let resources: PDFResources 11 | public let bbox: CGRect 12 | public let group: Group? 13 | public let matrix: CGAffineTransform? 14 | 15 | typealias Factory = (PDFObject) throws -> PDFXObject 16 | init(obj: PDFObject, parentStream: CGPDFContentStreamRef) throws { 17 | guard case let .stream(stream) = obj, 18 | case let dict = stream.dict, 19 | case let .name(type)? = dict["Type"], 20 | case let .name(subtype)? = dict["Subtype"] 21 | else { throw Error.parsingError() } 22 | guard type == "XObject" else { 23 | throw Error.unsupported("Only XObject type is supported") 24 | } 25 | guard !PDFXObject.unsupportedSubtypes.contains(subtype) else { 26 | throw Error 27 | .unsupported("XObject with subtype \(subtype) is not supported") 28 | } 29 | guard subtype == "Form" else { 30 | throw Error 31 | .unsupported( 32 | "XObject with subtype \(subtype) is not implemented (yet?)" 33 | ) 34 | } 35 | 36 | let contentStream = CGPDFContentStreamCreateWithStream( 37 | stream.raw, 38 | stream.rawDict, 39 | parentStream 40 | ) 41 | 42 | guard case let .array(bboxArray)? = dict["BBox"], 43 | let resourcesDict = dict["Resources"] 44 | else { throw Error.parsingError() } 45 | 46 | let resources = try PDFResources( 47 | obj: resourcesDict, 48 | parentStream: contentStream 49 | ) 50 | 51 | guard let bbox = CGRect.fromPDFArray(bboxArray) 52 | else { throw Error.parsingError() } 53 | 54 | let operators = try PDFContentStreamParser.parse(stream: contentStream) 55 | 56 | let group: Group? = if case let .dictionary(groupDict)? = stream 57 | .dict["Group"] { 58 | Group(dict: groupDict)! 59 | } else { 60 | nil 61 | } 62 | let matrix: CGAffineTransform? = if case let .array(matrixArray)? = stream 63 | .dict["Matrix"] { 64 | CGAffineTransform(pdfArray: matrixArray) 65 | } else { 66 | nil 67 | } 68 | 69 | let illegalKeys = 70 | Set(dict.keys).intersection(PDFXObject.unsupportedKeys) 71 | precondition(illegalKeys.isEmpty, "\(illegalKeys) are not supported") 72 | 73 | self.operators = operators 74 | self.resources = resources 75 | self.bbox = bbox 76 | self.group = group 77 | self.matrix = matrix 78 | } 79 | 80 | public struct Group { 81 | let colorSpace: PDFObject? 82 | public let isolated: Bool 83 | public let knockout: Bool 84 | 85 | init?(dict: [String: PDFObject]) { 86 | guard case let .name(subtype)? = dict["S"] 87 | else { return nil } 88 | precondition(subtype == "Transparency") 89 | colorSpace = dict["CS"] 90 | isolated = dict["I"]?.boolValue ?? false 91 | knockout = dict["K"]?.boolValue ?? false 92 | } 93 | } 94 | } 95 | 96 | extension PDFXObject: CustomStringConvertible { 97 | public var description: String { 98 | let groupDescr = String(describing: group) 99 | let matrixDescr = String(describing: matrix) 100 | return """ 101 | 102 | - resources: \(resources) 103 | - bbox: \(bbox) 104 | - group: \(groupDescr) 105 | - matrix: \(matrixDescr) 106 | """ 107 | } 108 | } 109 | --------------------------------------------------------------------------------