├── 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Demo/Demo/Images/heart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/various_filenames/Capital letter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/various_filenames/under_score.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/various_filenames/white space.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/path_smooth_curve.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/lines_and_curves.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/paths_and_images.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_stroke.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/miter_limit.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_absolute_start_end.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_radial.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/path_complex_curve.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_relative.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_shape.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/use_tag.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
22 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_transform_linear.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_units.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/path_circle_commands.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/alpha.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Demo/Demo/Images/mountain.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/path_fill_rule.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
44 | """
45 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/path_fill_rule_gstate.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_with_mask.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tests/RegressionTests/svg_samples/gradient_opacity.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------